PNG  IHDRQgAMA a cHRMz&u0`:pQ<bKGDgmIDATxwUﹻ& ^CX(J I@ "% (** BX +*i"]j(IH{~R)[~>h{}gy)I$Ij .I$I$ʊy@}x.: $I$Ii}VZPC)I$IF ^0ʐJ$I$Q^}{"r=OzI$gRZeC.IOvH eKX $IMpxsk.쒷/&r[޳<v| .I~)@$updYRa$I |M.e JaֶpSYR6j>h%IRز if&uJ)M$I vLi=H;7UJ,],X$I1AҒJ$ XY XzI@GNҥRT)E@;]K*Mw;#5_wOn~\ DC&$(A5 RRFkvIR}l!RytRl;~^ǷJj اy뷦BZJr&ӥ8Pjw~vnv X^(I;4R=P[3]J,]ȏ~:3?[ a&e)`e*P[4]T=Cq6R[ ~ޤrXR Հg(t_HZ-Hg M$ãmL5R uk*`%C-E6/%[t X.{8P9Z.vkXŐKjgKZHg(aK9ڦmKjѺm_ \#$5,)-  61eJ,5m| r'= &ڡd%-]J on Xm|{ RҞe $eڧY XYrԮ-a7RK6h>n$5AVڴi*ֆK)mѦtmr1p| q:흺,)Oi*ֺK)ܬ֦K-5r3>0ԔHjJئEZj,%re~/z%jVMڸmrt)3]J,T K֦OvԒgii*bKiNO~%PW0=dii2tJ9Jݕ{7"I P9JKTbu,%r"6RKU}Ij2HKZXJ,妝 XYrP ެ24c%i^IK|.H,%rb:XRl1X4Pe/`x&P8Pj28Mzsx2r\zRPz4J}yP[g=L) .Q[6RjWgp FIH*-`IMRaK9TXcq*I y[jE>cw%gLRԕiFCj-ďa`#e~I j,%r,)?[gp FI˨mnWX#>mʔ XA DZf9,nKҲzIZXJ,L#kiPz4JZF,I,`61%2s $,VOϚ2/UFJfy7K> X+6 STXIeJILzMfKm LRaK9%|4p9LwJI!`NsiazĔ)%- XMq>pk$-$Q2x#N ؎-QR}ᶦHZډ)J,l#i@yn3LN`;nڔ XuX5pF)m|^0(>BHF9(cզEerJI rg7 4I@z0\JIi䵙RR0s;$s6eJ,`n 䂦0a)S)A 1eJ,堌#635RIgpNHuTH_SԕqVe ` &S)>p;S$魁eKIuX`I4춒o}`m$1":PI<[v9^\pTJjriRŭ P{#{R2,`)e-`mgj~1ϣLKam7&U\j/3mJ,`F;M'䱀 .KR#)yhTq;pcK9(q!w?uRR,n.yw*UXj#\]ɱ(qv2=RqfB#iJmmL<]Y͙#$5 uTU7ӦXR+q,`I}qL'`6Kͷ6r,]0S$- [RKR3oiRE|nӦXR.(i:LDLTJjY%o:)6rxzҒqTJjh㞦I.$YR.ʼnGZ\ֿf:%55 I˼!6dKxm4E"mG_ s? .e*?LRfK9%q#uh$)i3ULRfK9yxm܌bj84$i1U^@Wbm4uJ,ҪA>_Ij?1v32[gLRD96oTaR׿N7%L2 NT,`)7&ƝL*꽙yp_$M2#AS,`)7$rkTA29_Iye"|/0t)$n XT2`YJ;6Jx".e<`$) PI$5V4]29SRI>~=@j]lp2`K9Jaai^" Ԋ29ORI%:XV5]JmN9]H;1UC39NI%Xe78t)a;Oi Ҙ>Xt"~G>_mn:%|~ޅ_+]$o)@ǀ{hgN;IK6G&rp)T2i୦KJuv*T=TOSV>(~D>dm,I*Ɛ:R#ۙNI%D>G.n$o;+#RR!.eU˽TRI28t)1LWϚ>IJa3oFbu&:tJ*(F7y0ZR ^p'Ii L24x| XRI%ۄ>S1]Jy[zL$adB7.eh4%%누>WETf+3IR:I3Xה)3אOۦSRO'ٺ)S}"qOr[B7ϙ.edG)^ETR"RtRݜh0}LFVӦDB^k_JDj\=LS(Iv─aTeZ%eUAM-0;~˃@i|l @S4y72>sX-vA}ϛBI!ݎߨWl*)3{'Y|iSlEڻ(5KtSI$Uv02,~ԩ~x;P4ցCrO%tyn425:KMlD ^4JRxSهF_}شJTS6uj+ﷸk$eZO%G*^V2u3EMj3k%)okI]dT)URKDS 7~m@TJR~荪fT"֛L \sM -0T KfJz+nإKr L&j()[E&I ߴ>e FW_kJR|!O:5/2跌3T-'|zX ryp0JS ~^F>-2< `*%ZFP)bSn"L :)+pʷf(pO3TMW$~>@~ū:TAIsV1}S2<%ޟM?@iT ,Eūoz%i~g|`wS(]oȤ8)$ ntu`өe`6yPl IzMI{ʣzʨ )IZ2= ld:5+請M$-ї;U>_gsY$ÁN5WzWfIZ)-yuXIfp~S*IZdt;t>KūKR|$#LcԀ+2\;kJ`]YǔM1B)UbG"IRߊ<xܾӔJ0Z='Y嵤 Leveg)$znV-º^3Ւof#0Tfk^Zs[*I꯳3{)ˬW4Ւ4 OdpbZRS|*I 55#"&-IvT&/윚Ye:i$ 9{LkuRe[I~_\ؠ%>GL$iY8 9ܕ"S`kS.IlC;Ҏ4x&>u_0JLr<J2(^$5L s=MgV ~,Iju> 7r2)^=G$1:3G< `J3~&IR% 6Tx/rIj3O< ʔ&#f_yXJiގNSz; Tx(i8%#4 ~AS+IjerIUrIj362v885+IjAhK__5X%nV%Iͳ-y|7XV2v4fzo_68"S/I-qbf; LkF)KSM$ Ms>K WNV}^`-큧32ŒVؙGdu,^^m%6~Nn&͓3ŒVZMsRpfEW%IwdǀLm[7W&bIRL@Q|)* i ImsIMmKmyV`i$G+R 0tV'!V)֏28vU7͒vHꦼtxꗞT ;S}7Mf+fIRHNZUkUx5SAJㄌ9MqμAIRi|j5)o*^'<$TwI1hEU^c_j?Е$%d`z cyf,XO IJnTgA UXRD }{H}^S,P5V2\Xx`pZ|Yk:$e ~ @nWL.j+ϝYb퇪bZ BVu)u/IJ_ 1[p.p60bC >|X91P:N\!5qUB}5a5ja `ubcVxYt1N0Zzl4]7­gKj]?4ϻ *[bg$)+À*x쳀ogO$~,5 زUS9 lq3+5mgw@np1sso Ӻ=|N6 /g(Wv7U;zωM=wk,0uTg_`_P`uz?2yI!b`kĸSo+Qx%!\οe|އԁKS-s6pu_(ֿ$i++T8=eY; צP+phxWQv*|p1. ά. XRkIQYP,drZ | B%wP|S5`~́@i޾ E;Չaw{o'Q?%iL{u D?N1BD!owPHReFZ* k_-~{E9b-~P`fE{AܶBJAFO wx6Rox5 K5=WwehS8 (JClJ~ p+Fi;ŗo+:bD#g(C"wA^ r.F8L;dzdIHUX݆ϞXg )IFqem%I4dj&ppT{'{HOx( Rk6^C٫O.)3:s(۳(Z?~ٻ89zmT"PLtw䥈5&b<8GZ-Y&K?e8,`I6e(֍xb83 `rzXj)F=l($Ij 2*(F?h(/9ik:I`m#p3MgLaKjc/U#n5S# m(^)=y=đx8ŬI[U]~SцA4p$-F i(R,7Cx;X=cI>{Km\ o(Tv2vx2qiiDJN,Ҏ!1f 5quBj1!8 rDFd(!WQl,gSkL1Bxg''՞^ǘ;pQ P(c_ IRujg(Wz bs#P­rz> k c&nB=q+ؔXn#r5)co*Ũ+G?7< |PQӣ'G`uOd>%Mctz# Ԫڞ&7CaQ~N'-P.W`Oedp03C!IZcIAMPUۀ5J<\u~+{9(FbbyAeBhOSܳ1 bÈT#ŠyDžs,`5}DC-`̞%r&ڙa87QWWp6e7 Rϫ/oY ꇅ Nܶըtc!LA T7V4Jsū I-0Pxz7QNF_iZgúWkG83 0eWr9 X]㾮݁#Jˢ C}0=3ݱtBi]_ &{{[/o[~ \q鯜00٩|cD3=4B_b RYb$óBRsf&lLX#M*C_L܄:gx)WΘsGSbuL rF$9';\4Ɍq'n[%p.Q`u hNb`eCQyQ|l_C>Lb꟟3hSb #xNxSs^ 88|Mz)}:](vbۢamŖ࿥ 0)Q7@0=?^k(*J}3ibkFn HjB׻NO z x}7p 0tfDX.lwgȔhԾŲ }6g E |LkLZteu+=q\Iv0쮑)QٵpH8/2?Σo>Jvppho~f>%bMM}\//":PTc(v9v!gոQ )UfVG+! 35{=x\2+ki,y$~A1iC6#)vC5^>+gǵ@1Hy٪7u;p psϰu/S <aʸGu'tD1ԝI<pg|6j'p:tպhX{o(7v],*}6a_ wXRk,O]Lܳ~Vo45rp"N5k;m{rZbΦ${#)`(Ŵg,;j%6j.pyYT?}-kBDc3qA`NWQū20/^AZW%NQ MI.X#P#,^Ebc&?XR tAV|Y.1!؅⨉ccww>ivl(JT~ u`ٵDm q)+Ri x/x8cyFO!/*!/&,7<.N,YDŽ&ܑQF1Bz)FPʛ?5d 6`kQձ λc؎%582Y&nD_$Je4>a?! ͨ|ȎWZSsv8 j(I&yj Jb5m?HWp=g}G3#|I,5v珿] H~R3@B[☉9Ox~oMy=J;xUVoj bUsl_35t-(ՃɼRB7U!qc+x4H_Qo֮$[GO<4`&č\GOc[.[*Af%mG/ ňM/r W/Nw~B1U3J?P&Y )`ѓZ1p]^l“W#)lWZilUQu`-m|xĐ,_ƪ|9i:_{*(3Gѧ}UoD+>m_?VPۅ15&}2|/pIOʵ> GZ9cmíتmnz)yߐbD >e}:) r|@R5qVSA10C%E_'^8cR7O;6[eKePGϦX7jb}OTGO^jn*媓7nGMC t,k31Rb (vyܴʭ!iTh8~ZYZp(qsRL ?b}cŨʊGO^!rPJO15MJ[c&~Z`"ѓޔH1C&^|Ш|rʼ,AwĴ?b5)tLU)F| &g٣O]oqSUjy(x<Ϳ3 .FSkoYg2 \_#wj{u'rQ>o;%n|F*O_L"e9umDds?.fuuQbIWz |4\0 sb;OvxOSs; G%T4gFRurj(֍ڑb uԖKDu1MK{1^ q; C=6\8FR艇!%\YÔU| 88m)֓NcLve C6z;o&X x59:q61Z(T7>C?gcļxѐ Z oo-08jہ x,`' ҔOcRlf~`jj".Nv+sM_]Zk g( UOPyεx%pUh2(@il0ݽQXxppx-NS( WO+轾 nFߢ3M<;z)FBZjciu/QoF 7R¥ ZFLF~#ȣߨ^<쩡ݛкvџ))ME>ώx4m#!-m!L;vv#~Y[đKmx9.[,UFS CVkZ +ߟrY٧IZd/ioi$%͝ب_ֶX3ܫhNU ZZgk=]=bbJS[wjU()*I =ώ:}-蹞lUj:1}MWm=̛ _ ¾,8{__m{_PVK^n3esw5ӫh#$-q=A̟> ,^I}P^J$qY~Q[ Xq9{#&T.^GVj__RKpn,b=`żY@^՝;z{paVKkQXj/)y TIc&F;FBG7wg ZZDG!x r_tƢ!}i/V=M/#nB8 XxЫ ^@CR<{䤭YCN)eKOSƟa $&g[i3.C6xrOc8TI;o hH6P&L{@q6[ Gzp^71j(l`J}]e6X☉#͕ ׈$AB1Vjh㭦IRsqFBjwQ_7Xk>y"N=MB0 ,C #o6MRc0|$)ف"1!ixY<B9mx `,tA>)5ػQ?jQ?cn>YZe Tisvh# GMމȇp:ԴVuږ8ɼH]C.5C!UV;F`mbBk LTMvPʍϤj?ԯ/Qr1NB`9s"s TYsz &9S%U԰> {<ؿSMxB|H\3@!U| k']$U+> |HHMLޢ?V9iD!-@x TIî%6Z*9X@HMW#?nN ,oe6?tQwڱ.]-y':mW0#!J82qFjH -`ѓ&M0u Uγmxϵ^-_\])@0Rt.8/?ٰCY]x}=sD3ojަЫNuS%U}ԤwHH>ڗjܷ_3gN q7[q2la*ArǓԖ+p8/RGM ]jacd(JhWko6ڎbj]i5Bj3+3!\j1UZLsLTv8HHmup<>gKMJj0@H%,W΃7R) ">c, xixј^ aܖ>H[i.UIHc U1=yW\=S*GR~)AF=`&2h`DzT󑓶J+?W+}C%P:|0H܆}-<;OC[~o.$~i}~HQ TvXΈr=b}$vizL4:ȰT|4~*!oXQR6Lk+#t/g lԁߖ[Jڶ_N$k*". xsxX7jRVbAAʯKҎU3)zSNN _'s?f)6X!%ssAkʱ>qƷb hg %n ~p1REGMHH=BJiy[<5 ǁJҖgKR*倳e~HUy)Ag,K)`Vw6bRR:qL#\rclK/$sh*$ 6덤 KԖc 3Z9=Ɣ=o>X Ώ"1 )a`SJJ6k(<c e{%kϊP+SL'TcMJWRm ŏ"w)qc ef꒵i?b7b('"2r%~HUS1\<(`1Wx9=8HY9m:X18bgD1u ~|H;K-Uep,, C1 RV.MR5άh,tWO8WC$ XRVsQS]3GJ|12 [vM :k#~tH30Rf-HYݺ-`I9%lIDTm\ S{]9gOڒMNCV\G*2JRŨ;Rҏ^ڽ̱mq1Eu?To3I)y^#jJw^Ńj^vvlB_⋌P4x>0$c>K†Aļ9s_VjTt0l#m>E-,,x,-W)سo&96RE XR.6bXw+)GAEvL)͞K4$p=Ũi_ѱOjb HY/+@θH9޼]Nԥ%n{ &zjT? Ty) s^ULlb,PiTf^<À] 62R^V7)S!nllS6~͝V}-=%* ʻ>G DnK<y&>LPy7'r=Hj 9V`[c"*^8HpcO8bnU`4JȪAƋ#1_\ XϘHPRgik(~G~0DAA_2p|J묭a2\NCr]M_0 ^T%e#vD^%xy-n}-E\3aS%yN!r_{ )sAw ڼp1pEAk~v<:`'ӭ^5 ArXOI驻T (dk)_\ PuA*BY]yB"l\ey hH*tbK)3 IKZ򹞋XjN n *n>k]X_d!ryBH ]*R 0(#'7 %es9??ښFC,ՁQPjARJ\Ρw K#jahgw;2$l*) %Xq5!U᢯6Re] |0[__64ch&_}iL8KEgҎ7 M/\`|.p,~`a=BR?xܐrQ8K XR2M8f ?`sgWS%" Ԉ 7R%$ N}?QL1|-эټwIZ%pvL3Hk>,ImgW7{E xPHx73RA @RS CC !\ȟ5IXR^ZxHл$Q[ŝ40 (>+ _C >BRt<,TrT {O/H+˟Pl6 I B)/VC<6a2~(XwV4gnXR ϱ5ǀHٻ?tw똤Eyxp{#WK qG%5],(0ӈH HZ])ג=K1j&G(FbM@)%I` XRg ʔ KZG(vP,<`[ Kn^ SJRsAʠ5xՅF`0&RbV tx:EaUE/{fi2;.IAwW8/tTxAGOoN?G}l L(n`Zv?pB8K_gI+ܗ #i?ޙ.) p$utc ~DžfՈEo3l/)I-U?aԅ^jxArA ΧX}DmZ@QLےbTXGd.^|xKHR{|ΕW_h] IJ`[G9{).y) 0X YA1]qp?p_k+J*Y@HI>^?gt.06Rn ,` ?);p pSF9ZXLBJPWjgQ|&)7! HjQt<| ؅W5 x W HIzYoVMGP Hjn`+\(dNW)F+IrS[|/a`K|ͻ0Hj{R,Q=\ (F}\WR)AgSG`IsnAR=|8$}G(vC$)s FBJ?]_u XRvύ6z ŨG[36-T9HzpW̞ú Xg큽=7CufzI$)ki^qk-) 0H*N` QZkk]/tnnsI^Gu't=7$ Z;{8^jB% IItRQS7[ϭ3 $_OQJ`7!]W"W,)Iy W AJA;KWG`IY{8k$I$^%9.^(`N|LJ%@$I}ֽp=FB*xN=gI?Q{٥4B)mw $Igc~dZ@G9K X?7)aK%݅K$IZ-`IpC U6$I\0>!9k} Xa IIS0H$I H ?1R.Чj:4~Rw@p$IrA*u}WjWFPJ$I➓/6#! LӾ+ X36x8J |+L;v$Io4301R20M I$-E}@,pS^ޟR[/s¹'0H$IKyfŸfVOπFT*a$I>He~VY/3R/)>d$I>28`Cjw,n@FU*9ttf$I~<;=/4RD~@ X-ѕzἱI$: ԍR a@b X{+Qxuq$IЛzo /~3\8ڒ4BN7$IҀj V]n18H$IYFBj3̵̚ja pp $Is/3R Ӻ-Yj+L;.0ŔI$Av? #!5"aʄj}UKmɽH$IjCYs?h$IDl843.v}m7UiI=&=0Lg0$I4: embe` eQbm0u? $IT!Sƍ'-sv)s#C0:XB2a w I$zbww{."pPzO =Ɔ\[ o($Iaw]`E).Kvi:L*#gР7[$IyGPI=@R 4yR~̮´cg I$I/<tPͽ hDgo 94Z^k盇΄8I56^W$I^0̜N?4*H`237}g+hxoq)SJ@p|` $I%>-hO0eO>\ԣNߌZD6R=K ~n($I$y3D>o4b#px2$yڪtzW~a $I~?x'BwwpH$IZݑnC㧄Pc_9sO gwJ=l1:mKB>Ab<4Lp$Ib o1ZQ@85b̍ S'F,Fe,^I$IjEdù{l4 8Ys_s Z8.x m"+{~?q,Z D!I$ϻ'|XhB)=…']M>5 rgotԎ 獽PH$IjIPhh)n#cÔqA'ug5qwU&rF|1E%I$%]!'3AFD/;Ck_`9 v!ٴtPV;x`'*bQa w I$Ix5 FC3D_~A_#O݆DvV?<qw+I$I{=Z8".#RIYyjǪ=fDl9%M,a8$I$Ywi[7ݍFe$s1ՋBVA?`]#!oz4zjLJo8$I$%@3jAa4(o ;p,,dya=F9ً[LSPH$IJYЉ+3> 5"39aZ<ñh!{TpBGkj}Sp $IlvF.F$I z< '\K*qq.f<2Y!S"-\I$IYwčjF$ w9 \ߪB.1v!Ʊ?+r:^!I$BϹB H"B;L'G[ 4U#5>੐)|#o0aڱ$I>}k&1`U#V?YsV x>{t1[I~D&(I$I/{H0fw"q"y%4 IXyE~M3 8XψL}qE$I[> nD?~sf ]o΁ cT6"?'_Ἣ $I>~.f|'!N?⟩0G KkXZE]ޡ;/&?k OۘH$IRۀwXӨ<7@PnS04aӶp.:@\IWQJ6sS%I$e5ڑv`3:x';wq_vpgHyXZ 3gЂ7{{EuԹn±}$I$8t;b|591nءQ"P6O5i }iR̈́%Q̄p!I䮢]O{H$IRϻ9s֧ a=`- aB\X0"+5"C1Hb?߮3x3&gşggl_hZ^,`5?ߎvĸ%̀M!OZC2#0x LJ0 Gw$I$I}<{Eb+y;iI,`ܚF:5ܛA8-O-|8K7s|#Z8a&><a&/VtbtLʌI$I$I$I$I$I$IRjDD%tEXtdate:create2022-05-31T04:40:26+00:00!Î%tEXtdate:modify2022-05-31T04:40:26+00:00|{2IENDB`Mini Shell

HOME


Mini Shell 1.0
DIR:/opt/cloudlinux/venv/lib/python3.11/site-packages/lvestats/plugins/generic/
Upload File :
Current File : //opt/cloudlinux/venv/lib/python3.11/site-packages/lvestats/plugins/generic/snapshot_saver.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 logging
import string
from random import choice
from typing import Dict, List, Optional, Tuple  # NOQA

import sqlalchemy.orm.session  # NOQA
from sqlalchemy.orm import sessionmaker

from clcommon.clpwd import ClPwd
from clcommon.cpapi import NotSupported
from clcommon.cpapi.cpapiexceptions import NoDBAccessData
from clcommon.utils import ExternalProgramFailed
from lvestats.core.plugin import LveStatsPlugin
from lvestats.lib.commons import proctitle
from lvestats.lib.commons.func import get_chunks, reboot_lock
from lvestats.lib.commons.htpasswd import HtpasswdFile
from lvestats.lib.commons.litespeed import LiteSpeed, LiteSpeedException, LiteSpeedInvalidCredentials
from lvestats.lib.snapshot import Snapshot
from lvestats.lib.ustate import MySQLOperationalError, SQLSnapshot, get_lveps
from lvestats.orm.incident import incident

DEFAULT_PERIOD_BETWEEN_INCIDENTS = 300  # time separating incidents
DEFAULT_SNAPSHOTS_PER_MINUTE = 2  # number of snapshots per minute
DEFAULT_MAX_SNAPSHOTS_PER_INCIDENT = 10  # the maximum number of snapshots in the incident
APACHE = 'apache'
LITESPEED = 'litespeed'


class LitespeedHelper(object):
    def __init__(self):
        self.is_running = False
        self.state_changed = False

        # config option; None if server should be
        # detected automatically,
        # False if we must use apache
        # True if we must use litespeed
        self.force_litespeed = None

        # create random login-password pair
        self.login = 'lve-stats-admin'
        self.password = self.generate_random_password()

        self.broken_config = False

    def check_litespeed_state(self):
        """Check litespeed state"""
        litespeed_running = LiteSpeed.is_litespeed_running()
        self.state_changed = litespeed_running != self.is_running
        self.is_running = litespeed_running

    def dump_passwd(self):
        try:
            passwdfile = HtpasswdFile(LiteSpeed.HTPASSWD_PATH)
        except ValueError:
            self.broken_config = True
            logging.warning("Can't change the password. Please, check the file:\n '%s'", LiteSpeed.HTPASSWD_PATH)
            return
        passwdfile.update(self.login, self.password)
        passwdfile.save()
        logging.debug("Password successfully changed.")

    def get_user_data(self, username):
        # type: (str) -> list
        """Get user data proxy method"""
        litespeed = LiteSpeed(self.login, self.password)
        try:
            return litespeed.get_user_data(username)
        except LiteSpeedInvalidCredentials:
            self.dump_passwd()
            raise

    @staticmethod
    def generate_random_password():
        # type: () -> str
        chars = string.ascii_letters + string.digits + '!@#$%^&*()'
        return ''.join(choice(chars) for _ in range(16))

    def get_use_litespeed(self):
        # type: () -> bool
        """Get what we must use: litespeed or apache"""
        is_litespeed_running = self.is_running
        if self.broken_config:
            return False
        elif self.force_litespeed is not None:
            return self.force_litespeed

        return is_litespeed_running


class SnapshotHelper(object):
    username_dbquery_map = None
    ps = {}
    clpwd = None
    sql_snap = SQLSnapshot()

    def __init__(self):
        self._mysql_snapshots_enabled = None
        self.litespeed_died = False
        self._sql_snapshot_supported = None
        self._no_operational_error = None
        self.log = logging.getLogger('SnapshotSaver')

    def set_config(self, mysql_snapshots_enabled):
        self._mysql_snapshots_enabled = mysql_snapshots_enabled

    def get_names(self, lve_id):
        # type: (int) -> List[str]
        try:
            return self.clpwd.get_names(lve_id)
        except ClPwd.NoSuchUserException:
            return []

    def get_snapshot_data(self, lve_id, litespeed_info):
        # type: (int, LitespeedHelper) -> Tuple[dict, list, list]
        processes = self.ps.get(lve_id, {}).get('TID', {})
        queries = []
        urls = []
        for username in self.get_names(lve_id):
            queries += self.username_dbquery_map.get(username, [])
            use_litespeed = litespeed_info.get_use_litespeed()
            if use_litespeed:
                try:
                    urls += litespeed_info.get_user_data(username)
                except LiteSpeedException as e:
                    # do not save message every time
                    if not self.litespeed_died:
                        logging.warning('Error during getting information from Litespeed: %s', e)
                        self.litespeed_died = True
                    urls += proctitle.Proctitle().get_user_data(username)
            else:
                urls += proctitle.Proctitle().get_user_data(username)
        return processes, queries, urls

    def invalidate(self, lve_id_list):
        # type: (List[int]) -> None
        self.clpwd = ClPwd()
        all_usernames = []
        for lve_id in lve_id_list:
            all_usernames += self.get_names(int(lve_id))
        self.username_dbquery_map = (
            self.retrieve_queries(all_usernames))
        try:
            self.ps = get_lveps()
        except ExternalProgramFailed as e:
            self.log.warning('An error occurred while getting processes list', exc_info=e)

    def retrieve_queries(self, login_names):
        # type: (Optional[List[str]]) -> Dict[str, List[str]]
        if not self._mysql_snapshots_enabled:
            return {}
        try:
            with self.sql_snap as db_requests:
                result = db_requests.get(login_names)
                if not self._sql_snapshot_supported:
                    self._sql_snapshot_supported = True
                    self.log.info('SQL snapshot is supported and operational')
                self._no_operational_error = True
                return result
        except MySQLOperationalError as e:
            if self._no_operational_error in [None, True]:
                self._no_operational_error = False
                self.log.warning('An error occurred while getting MySQL process list', exc_info=e)
        except (NotSupported, NoDBAccessData) as e:
            # errors which we can write only once
            # because who needs this message in log each 5 seconds?
            if self._sql_snapshot_supported in [None, True]:
                self._sql_snapshot_supported = False
                self.log.info('SQL snapshot is not supported by this control panel', exc_info=e)
        return {}


class SnapshotSaver(LveStatsPlugin):
    server_id = ''
    period_between_incidents = DEFAULT_PERIOD_BETWEEN_INCIDENTS
    max_snapshots_per_incident = DEFAULT_MAX_SNAPSHOTS_PER_INCIDENT
    _period = 60 // DEFAULT_SNAPSHOTS_PER_MINUTE

    def __init__(self):
        self.incidents_last_ts = {}
        self.log = logging.getLogger('SnapshotSaver')
        self.session = None  # type: sqlalchemy.orm.session.Session
        self._snapshots_data = {}
        self.incidents_cache = {}
        self.first_run = True
        self.last_run = 0
        self.litespeed_info = LitespeedHelper()
        self.snapshots_enabled = True
        self.mysql_snapshots_enabled = True
        self.compresslevel = 1
        self._helper = SnapshotHelper()

    def set_config(self, config):
        # type: (dict) -> None
        self.server_id = config.get('server_id', self.server_id)
        self.period_between_incidents = int(config.get('period_between_incidents', self.period_between_incidents))
        self.max_snapshots_per_incident = int(config.get('max_snapshots_per_incident', self.max_snapshots_per_incident))
        self._period = 60 // int(config.get('snapshots_per_minute', DEFAULT_SNAPSHOTS_PER_MINUTE))
        self.litespeed_info.force_litespeed = self._get_webserver_option(config)
        self.setup_litespeed(force_webserver_message=True)
        self.snapshots_enabled = config.get('disable_snapshots', "false").lower() != "true"
        self.mysql_snapshots_enabled = config.get('disable_mysql_snapshots', "false").lower() != "true"
        self.compresslevel = max(min(int(config.get('compresslevel', 1)), 9), 1)

        self._helper.set_config(self.mysql_snapshots_enabled)

    def _get_webserver_option(self, config):
        # type: (dict) -> Optional[bool]
        """
        Check which webserver we must force to use: Apache or Litespeed
        :return: None if webserver autodetect
                 False if apache should be used
                 True if litespeed should be used
        """
        if 'litespeed' in config:
            option = config['litespeed'].lower()
            if option in ['on', '1']:
                return True

            if option in ['off', '0']:
                return False
        return None

    def _incomplete_incidents_query(self):
        # type: () -> sqlalchemy.orm.query.Query
        """Generate sqlalchemy Query instance for select incomplete incidents"""
        return self.session.query(incident).filter(
            incident.server_id == self.server_id, incident.incident_end_time.is_(None)
        )

    def finalize_incidents(self):
        # type: () -> None
        not_finalize_incidents_query = self._incomplete_incidents_query().filter(
            incident.dump_time < self.now - self.period_between_incidents
        )
        finalized_numb = not_finalize_incidents_query.update({incident.incident_end_time: incident.dump_time})
        self.log.debug('%i old incidents period was finalized', finalized_numb)
        self.session.commit()

    def save_old_incidents(self):
        # type: () -> None
        old_incidents = {
            uid: ts for uid, ts in list(self.incidents_last_ts.items()) if ts < self.now - self.period_between_incidents
        }
        if old_incidents:
            try:
                for _inc in (
                    self.session.query(incident)
                    .filter(
                        incident.server_id == self.server_id,
                        incident.incident_end_time.is_(None),
                        incident.uid.in_(list(old_incidents.keys())),
                    )
                    .all()
                ):
                    _inc.incident_end_time = old_incidents[_inc.uid]
            except Exception as e:
                self.session.rollback()
                self.log.error("Unable to save old incidents: (%s)", str(e))
            else:
                self.session.commit()
            for uid in list(old_incidents.keys()):
                self.incidents_last_ts.pop(uid)
                try:
                    self.incidents_cache.pop(uid)
                except KeyError:
                    pass

    def get_incident(self, uid):
        # type: (int) -> incident
        _inc = self._incomplete_incidents_query().filter(incident.uid == uid).first()
        if not _inc:
            _inc = incident(
                uid=uid,
                incident_start_time=self.now,
                server_id=self.server_id,
                snapshot_count=0,
                incident_end_time=None,
            )
            self.session.add(_inc)
            self.log.debug(
                'New incident-period for uid %s started; incident_start_time=%i',
                uid,
                self.now,
            )
        self.incidents_last_ts[uid] = self.now
        return _inc

    def init_session(self):
        if self.first_run:  # Running for the first time
            self.session = sessionmaker(bind=self.engine)()
            self.finalize_incidents()
            self.first_run = False

    def process_lve(self, lve_id, faults):
        # type: (int, Dict[str, int]) -> None
        try:
            lve_id = int(lve_id)
            self.incidents_last_ts[lve_id] = self.now
            _inc = self.incidents_cache[lve_id]
            self.log.debug(
                'Faults %s for uid %s detected; timestamp %i',
                faults,
                lve_id,
                self.now,
            )
            if _inc["snapshot_count"] < self.max_snapshots_per_incident:
                self.save_snapshot(_inc, faults)
                self.log.debug(
                    'Snapshot for uid %s with timestamp %i saved',
                    lve_id,
                    self.now,
                )
        except Exception as e:
            self.session.rollback()
            self.log.warning("Unable to save incident for LVE %s (%s)", lve_id, e)

    def setup_litespeed(self, force_webserver_message=False):
        # type: (bool) -> None
        """Check state and configure access to litespeed"""
        self.litespeed_info.check_litespeed_state()
        if self.litespeed_info.state_changed and self.litespeed_info.is_running:
            self.litespeed_info.dump_passwd()

        if self.litespeed_info.state_changed or force_webserver_message:
            use_litespeed = self.litespeed_info.get_use_litespeed()
            litespeed_running = self.litespeed_info.is_running
            if use_litespeed and not litespeed_running:
                self.log.info("Litespeed is not running properly. Check litespeed license key.")
            webserver = LITESPEED if use_litespeed else APACHE
            msg = f"{webserver} webserver will be used now to obtain data"
            self.log.info(msg)

    def execute(self, lve_data):
        # type: (dict) -> None
        if not self.snapshots_enabled:
            return

        self.init_session()
        self.setup_litespeed()
        lve_ids = list(lve_data.get('lve_usage', {}).keys())
        lve_faults = lve_data.get('faults', {})
        self._helper.invalidate(lve_ids)
        self.cache_snapshots(lve_faults)

        if self.now - self.last_run >= self._period:
            self.last_run = self.now
            with reboot_lock():
                self.save_old_incidents()
                self._insert_new_incidents(lve_faults)
                self._increment_snapshot_count(lve_faults)
                for lve_id, faults in list(lve_faults.items()):
                    self.process_lve(lve_id, faults)
            lve_data["faults"] = {}
            self._snapshots_data = {}

    def _increment_snapshot_count(self, lve_faults):
        # type: (Dict[int, Dict[str, int]]) -> None
        lve_id_list = list(lve_faults.keys())
        for chunk in get_chunks(lve_id_list, 250):
            self._incomplete_incidents_query().filter(
                incident.uid.in_(chunk), incident.snapshot_count < self.max_snapshots_per_incident
            ).update(
                {"snapshot_count": incident.snapshot_count + 1, "dump_time": int(self.now)}, synchronize_session=False
            )
            self.session.commit()

    def _insert_new_incidents(self, lve_faults):
        # type: (Dict[int, Dict[str, int]]) -> None
        new_incidents = {
            lve_id: {
                'uid': lve_id,
                "incident_start_time": self.now,
                "server_id": self.server_id,
                "snapshot_count": 0,
                "incident_end_time": None,
            }
            for lve_id, _ in list(lve_faults.items())
            if lve_id not in self.incidents_cache
        }
        if new_incidents:
            self.incidents_cache.update(new_incidents)
            insert_list = (incident(**_inc) for _inc in list(new_incidents.values()))
            try:
                # Better to use
                # self.session.bulk_insert_mappings(incident, new_incidents)
                # but it will be available only in SQLAlchemy version > 1.0
                self.session.add_all(insert_list)
            except Exception as e:
                self.log.error("Unable to save new incidents: %s", str(e))
            else:
                self.session.commit()

    def cache_snapshots(self, lve_faults):
        # type: (Dict[int, Dict[str, int]]) -> None
        for lve_id in list(lve_faults.keys()):
            if lve_id not in self._snapshots_data:
                self._snapshots_data[lve_id] = self._helper.get_snapshot_data(lve_id, self.litespeed_info)

    def save_snapshot(self, _incident, faults):
        # type: (dict, Dict[str, int]) -> None
        lve_id = _incident["uid"]
        processes, queries, urls = self._snapshots_data.get(lve_id, ({}, [], []))
        snapshot_ = Snapshot(_incident, self.compresslevel)
        data = {
            'uid': _incident["uid"],
            'dump_time': int(self.now),
            'server_id': self.server_id,
            'incident_start_time': _incident["incident_start_time"],
            'snap_proc': processes,
            'snap_sql': queries,
            'snap_http': urls,
            'snap_faults': faults,
        }
        try:
            snapshot_.save(data)
        except IOError as e:
            self.log.error(str(e))
        _incident["snapshot_count"] += 1