PNG  IHDRX cHRMz&u0`:pQ<bKGD pHYsodtIME MeqIDATxw]Wug^Qd˶ 6`!N:!@xI~)%7%@Bh&`lnjVF29gΨ4E$|>cɚ{gk= %,a KX%,a KX%,a KX%,a KX%,a KX%,a KX%, b` ǟzeאfp]<!SJmɤY޲ڿ,%c ~ع9VH.!Ͳz&QynֺTkRR.BLHi٪:l;@(!MԴ=žI,:o&N'Kù\vRmJ雵֫AWic H@" !: Cé||]k-Ha oݜ:y F())u]aG7*JV@J415p=sZH!=!DRʯvɱh~V\}v/GKY$n]"X"}t@ xS76^[bw4dsce)2dU0 CkMa-U5tvLƀ~mlMwfGE/-]7XAƟ`׮g ewxwC4\[~7@O-Q( a*XGƒ{ ՟}$_y3tĐƤatgvێi|K=uVyrŲlLӪuܿzwk$m87k( `múcE)"@rK( z4$D; 2kW=Xb$V[Ru819קR~qloѱDyįݎ*mxw]y5e4K@ЃI0A D@"BDk_)N\8͜9dz"fK0zɿvM /.:2O{ Nb=M=7>??Zuo32 DLD@D| &+֎C #B8ַ`bOb $D#ͮҪtx]%`ES`Ru[=¾!@Od37LJ0!OIR4m]GZRJu$‡c=%~s@6SKy?CeIh:[vR@Lh | (BhAMy=݃  G"'wzn޺~8ԽSh ~T*A:xR[ܹ?X[uKL_=fDȊ؂p0}7=D$Ekq!/t.*2ʼnDbŞ}DijYaȲ(""6HA;:LzxQ‘(SQQ}*PL*fc\s `/d'QXW, e`#kPGZuŞuO{{wm[&NBTiiI0bukcA9<4@SӊH*؎4U/'2U5.(9JuDfrޱtycU%j(:RUbArLֺN)udA':uGQN"-"Is.*+k@ `Ojs@yU/ H:l;@yyTn}_yw!VkRJ4P)~y#)r,D =ě"Q]ci'%HI4ZL0"MJy 8A{ aN<8D"1#IJi >XjX֔#@>-{vN!8tRݻ^)N_╗FJEk]CT՟ YP:_|H1@ CBk]yKYp|og?*dGvzنzӴzjֺNkC~AbZƷ`.H)=!QͷVTT(| u78y֮}|[8-Vjp%2JPk[}ԉaH8Wpqhwr:vWª<}l77_~{s۴V+RCģ%WRZ\AqHifɤL36: #F:p]Bq/z{0CU6ݳEv_^k7'>sq*+kH%a`0ԣisqにtү04gVgW΂iJiS'3w.w}l6MC2uԯ|>JF5`fV5m`Y**Db1FKNttu]4ccsQNnex/87+}xaUW9y>ͯ骵G{䩓Գ3+vU}~jJ.NFRD7<aJDB1#ҳgSb,+CS?/ VG J?|?,2#M9}B)MiE+G`-wo߫V`fio(}S^4e~V4bHOYb"b#E)dda:'?}׮4繏`{7Z"uny-?ǹ;0MKx{:_pÚmFמ:F " .LFQLG)Q8qN q¯¯3wOvxDb\. BKD9_NN &L:4D{mm o^tֽ:q!ƥ}K+<"m78N< ywsard5+вz~mnG)=}lYݧNj'QJS{S :UYS-952?&O-:W}(!6Mk4+>A>j+i|<<|;ر^߉=HE|V#F)Emm#}/"y GII웻Jі94+v뾧xu~5C95~ūH>c@덉pʃ1/4-A2G%7>m;–Y,cyyaln" ?ƻ!ʪ<{~h~i y.zZB̃/,雋SiC/JFMmBH&&FAbϓO^tubbb_hZ{_QZ-sύodFgO(6]TJA˯#`۶ɟ( %$&+V'~hiYy>922 Wp74Zkq+Ovn錄c>8~GqܲcWꂎz@"1A.}T)uiW4="jJ2W7mU/N0gcqܗOO}?9/wìXžΏ0 >֩(V^Rh32!Hj5`;O28؇2#ݕf3 ?sJd8NJ@7O0 b־?lldщ̡&|9C.8RTWwxWy46ah嘦mh٤&l zCy!PY?: CJyв]dm4ǜҐR޻RլhX{FƯanшQI@x' ao(kUUuxW_Ñ줮[w8 FRJ(8˼)_mQ _!RJhm=!cVmm ?sFOnll6Qk}alY}; "baӌ~M0w,Ggw2W:G/k2%R,_=u`WU R.9T"v,<\Ik޽/2110Ӿxc0gyC&Ny޽JҢrV6N ``یeA16"J³+Rj*;BϜkZPJaÍ<Jyw:NP8/D$ 011z֊Ⱳ3ι֘k1V_"h!JPIΣ'ɜ* aEAd:ݺ>y<}Lp&PlRfTb1]o .2EW\ͮ]38؋rTJsǏP@芎sF\> P^+dYJLbJ C-xϐn> ι$nj,;Ǖa FU *择|h ~izť3ᤓ`K'-f tL7JK+vf2)V'-sFuB4i+m+@My=O҈0"|Yxoj,3]:cо3 $#uŘ%Y"y죯LebqtҢVzq¼X)~>4L׶m~[1_k?kxֺQ`\ |ٛY4Ѯr!)N9{56(iNq}O()Em]=F&u?$HypWUeB\k]JɩSع9 Zqg4ZĊo oMcjZBU]B\TUd34ݝ~:7ڶSUsB0Z3srx 7`:5xcx !qZA!;%͚7&P H<WL!džOb5kF)xor^aujƍ7 Ǡ8/p^(L>ὴ-B,{ۇWzֺ^k]3\EE@7>lYBȝR.oHnXO/}sB|.i@ɥDB4tcm,@ӣgdtJ!lH$_vN166L__'Z)y&kH;:,Y7=J 9cG) V\hjiE;gya~%ks_nC~Er er)muuMg2;֫R)Md) ,¶ 2-wr#F7<-BBn~_(o=KO㭇[Xv eN_SMgSҐ BS헃D%g_N:/pe -wkG*9yYSZS.9cREL !k}<4_Xs#FmҶ:7R$i,fi!~' # !6/S6y@kZkZcX)%5V4P]VGYq%H1!;e1MV<!ϐHO021Dp= HMs~~a)ަu7G^];git!Frl]H/L$=AeUvZE4P\.,xi {-~p?2b#amXAHq)MWǾI_r`S Hz&|{ +ʖ_= (YS(_g0a03M`I&'9vl?MM+m~}*xT۲(fY*V4x@29s{DaY"toGNTO+xCAO~4Ϳ;p`Ѫ:>Ҵ7K 3}+0 387x\)a"/E>qpWB=1 ¨"MP(\xp߫́A3+J] n[ʼnӼaTbZUWb={~2ooKױӰp(CS\S筐R*JغV&&"FA}J>G֐p1ٸbk7 ŘH$JoN <8s^yk_[;gy-;߉DV{c B yce% aJhDȶ 2IdйIB/^n0tNtџdcKj4϶v~- CBcgqx9= PJ) dMsjpYB] GD4RDWX +h{y`,3ꊕ$`zj*N^TP4L:Iz9~6s) Ga:?y*J~?OrMwP\](21sZUD ?ܟQ5Q%ggW6QdO+\@ ̪X'GxN @'4=ˋ+*VwN ne_|(/BDfj5(Dq<*tNt1х!MV.C0 32b#?n0pzj#!38}޴o1KovCJ`8ŗ_"]] rDUy޲@ Ȗ-;xџ'^Y`zEd?0„ DAL18IS]VGq\4o !swV7ˣι%4FѮ~}6)OgS[~Q vcYbL!wG3 7띸*E Pql8=jT\꘿I(z<[6OrR8ºC~ډ]=rNl[g|v TMTղb-o}OrP^Q]<98S¤!k)G(Vkwyqyr޽Nv`N/e p/~NAOk \I:G6]4+K;j$R:Mi #*[AȚT,ʰ,;N{HZTGMoּy) ]%dHء9Պ䠬|<45,\=[bƟ8QXeB3- &dҩ^{>/86bXmZ]]yޚN[(WAHL$YAgDKp=5GHjU&99v簪C0vygln*P)9^͞}lMuiH!̍#DoRBn9l@ xA/_v=ȺT{7Yt2N"4!YN`ae >Q<XMydEB`VU}u]嫇.%e^ánE87Mu\t`cP=AD/G)sI"@MP;)]%fH9'FNsj1pVhY&9=0pfuJ&gޤx+k:!r˭wkl03׼Ku C &ѓYt{.O.zҏ z}/tf_wEp2gvX)GN#I ݭ߽v/ .& и(ZF{e"=V!{zW`, ]+LGz"(UJp|j( #V4, 8B 0 9OkRrlɱl94)'VH9=9W|>PS['G(*I1==C<5"Pg+x'K5EMd؞Af8lG ?D FtoB[je?{k3zQ vZ;%Ɠ,]E>KZ+T/ EJxOZ1i #T<@ I}q9/t'zi(EMqw`mYkU6;[t4DPeckeM;H}_g pMww}k6#H㶏+b8雡Sxp)&C $@'b,fPߑt$RbJ'vznuS ~8='72_`{q纶|Q)Xk}cPz9p7O:'|G~8wx(a 0QCko|0ASD>Ip=4Q, d|F8RcU"/KM opKle M3#i0c%<7׿p&pZq[TR"BpqauIp$ 8~Ĩ!8Սx\ւdT>>Z40ks7 z2IQ}ItԀ<-%S⍤};zIb$I 5K}Q͙D8UguWE$Jh )cu4N tZl+[]M4k8֦Zeq֮M7uIqG 1==tLtR,ƜSrHYt&QP윯Lg' I,3@P'}'R˪e/%-Auv·ñ\> vDJzlӾNv5:|K/Jb6KI9)Zh*ZAi`?S {aiVDԲuy5W7pWeQJk֤#5&V<̺@/GH?^τZL|IJNvI:'P=Ϛt"¨=cud S Q.Ki0 !cJy;LJR;G{BJy޺[^8fK6)=yʊ+(k|&xQ2`L?Ȓ2@Mf 0C`6-%pKpm')c$׻K5[J*U[/#hH!6acB JA _|uMvDyk y)6OPYjœ50VT K}cǻP[ $:]4MEA.y)|B)cf-A?(e|lɉ#P9V)[9t.EiQPDѠ3ϴ;E:+Օ t ȥ~|_N2,ZJLt4! %ա]u {+=p.GhNcŞQI?Nd'yeh n7zi1DB)1S | S#ًZs2|Ɛy$F SxeX{7Vl.Src3E℃Q>b6G ўYCmtկ~=K0f(=LrAS GN'ɹ9<\!a`)֕y[uՍ[09` 9 +57ts6}b4{oqd+J5fa/,97J#6yν99mRWxJyѡyu_TJc`~W>l^q#Ts#2"nD1%fS)FU w{ܯ R{ ˎ󅃏џDsZSQS;LV;7 Od1&1n$ N /.q3~eNɪ]E#oM~}v֯FڦwyZ=<<>Xo稯lfMFV6p02|*=tV!c~]fa5Y^Q_WN|Vs 0ҘދU97OI'N2'8N֭fgg-}V%y]U4 峧p*91#9U kCac_AFңĪy뚇Y_AiuYyTTYЗ-(!JFLt›17uTozc. S;7A&&<ԋ5y;Ro+:' *eYJkWR[@F %SHWP 72k4 qLd'J "zB6{AC0ƁA6U.'F3:Ȅ(9ΜL;D]m8ڥ9}dU "v!;*13Rg^fJyShyy5auA?ɩGHRjo^]׽S)Fm\toy 4WQS@mE#%5ʈfFYDX ~D5Ϡ9tE9So_aU4?Ѽm%&c{n>.KW1Tlb}:j uGi(JgcYj0qn+>) %\!4{LaJso d||u//P_y7iRJ߬nHOy) l+@$($VFIQ9%EeKʈU. ia&FY̒mZ=)+qqoQn >L!qCiDB;Y<%} OgBxB!ØuG)WG9y(Ą{_yesuZmZZey'Wg#C~1Cev@0D $a@˲(.._GimA:uyw֬%;@!JkQVM_Ow:P.s\)ot- ˹"`B,e CRtaEUP<0'}r3[>?G8xU~Nqu;Wm8\RIkբ^5@k+5(By'L&'gBJ3ݶ!/㮻w҅ yqPWUg<e"Qy*167΃sJ\oz]T*UQ<\FԎ`HaNmڜ6DysCask8wP8y9``GJ9lF\G g's Nn͵MLN֪u$| /|7=]O)6s !ĴAKh]q_ap $HH'\1jB^s\|- W1:=6lJBqjY^LsPk""`]w)󭃈,(HC ?䔨Y$Sʣ{4Z+0NvQkhol6C.婧/u]FwiVjZka&%6\F*Ny#8O,22+|Db~d ~Çwc N:FuuCe&oZ(l;@ee-+Wn`44AMK➝2BRՈt7g*1gph9N) *"TF*R(#'88pm=}X]u[i7bEc|\~EMn}P瘊J)K.0i1M6=7'_\kaZ(Th{K*GJyytw"IO-PWJk)..axӝ47"89Cc7ĐBiZx 7m!fy|ϿF9CbȩV 9V-՛^pV̌ɄS#Bv4-@]Vxt-Z, &ֺ*diؠ2^VXbs֔Ìl.jQ]Y[47gj=幽ex)A0ip׳ W2[ᎇhuE^~q흙L} #-b۸oFJ_QP3r6jr+"nfzRJTUqoaۍ /$d8Mx'ݓ= OՃ| )$2mcM*cЙj}f };n YG w0Ia!1Q.oYfr]DyISaP}"dIӗթO67jqR ҊƐƈaɤGG|h;t]䗖oSv|iZqX)oalv;۩meEJ\!8=$4QU4Xo&VEĊ YS^E#d,yX_> ۘ-e\ "Wa6uLĜZi`aD9.% w~mB(02G[6y.773a7 /=o7D)$Z 66 $bY^\CuP. (x'"J60׿Y:Oi;F{w佩b+\Yi`TDWa~|VH)8q/=9!g߆2Y)?ND)%?Ǐ`k/sn:;O299yB=a[Ng 3˲N}vLNy;*?x?~L&=xyӴ~}q{qE*IQ^^ͧvü{Huu=R|>JyUlZV, B~/YF!Y\u_ݼF{_C)LD]m {H 0ihhadd nUkf3oٺCvE\)QJi+֥@tDJkB$1!Đr0XQ|q?d2) Ӣ_}qv-< FŊ߫%roppVBwü~JidY4:}L6M7f٬F "?71<2#?Jyy4뷢<_a7_=Q E=S1И/9{+93֮E{ǂw{))?maÆm(uLE#lïZ  ~d];+]h j?!|$F}*"4(v'8s<ŏUkm7^7no1w2ؗ}TrͿEk>p'8OB7d7R(A 9.*Mi^ͳ; eeUwS+C)uO@ =Sy]` }l8^ZzRXj[^iUɺ$tj))<sbDJfg=Pk_{xaKo1:-uyG0M ԃ\0Lvuy'ȱc2Ji AdyVgVh!{]/&}}ċJ#%d !+87<;qN޼Nفl|1N:8ya  8}k¾+-$4FiZYÔXk*I&'@iI99)HSh4+2G:tGhS^繿 Kتm0 вDk}֚+QT4;sC}rՅE,8CX-e~>G&'9xpW,%Fh,Ry56Y–hW-(v_,? ; qrBk4-V7HQ;ˇ^Gv1JVV%,ik;D_W!))+BoS4QsTM;gt+ndS-~:11Sgv!0qRVh!"Ȋ(̦Yl.]PQWgٳE'`%W1{ndΗBk|Ž7ʒR~,lnoa&:ü$ 3<a[CBݮwt"o\ePJ=Hz"_c^Z.#ˆ*x z̝grY]tdkP*:97YľXyBkD4N.C_[;F9`8& !AMO c `@BA& Ost\-\NX+Xp < !bj3C&QL+*&kAQ=04}cC!9~820G'PC9xa!w&bo_1 Sw"ܱ V )Yl3+ס2KoXOx]"`^WOy :3GO0g;%Yv㐫(R/r (s } u B &FeYZh0y> =2<Ϟc/ -u= c&׭,.0"g"7 6T!vl#sc>{u/Oh Bᾈ)۴74]x7 gMӒ"d]U)}" v4co[ ɡs 5Gg=XR14?5A}D "b{0$L .\4y{_fe:kVS\\O]c^W52LSBDM! C3Dhr̦RtArx4&agaN3Cf<Ԉp4~ B'"1@.b_/xQ} _߃҉/gٓ2Qkqp0շpZ2fԫYz< 4L.Cyυι1t@鎫Fe sYfsF}^ V}N<_`p)alٶ "(XEAVZ<)2},:Ir*#m_YӼ R%a||EƼIJ,,+f"96r/}0jE/)s)cjW#w'Sʯ5<66lj$a~3Kʛy 2:cZ:Yh))+a߭K::N,Q F'qB]={.]h85C9cr=}*rk?vwV렵ٸW Rs%}rNAkDv|uFLBkWY YkX מ|)1!$#3%y?pF<@<Rr0}: }\J [5FRxY<9"SQdE(Q*Qʻ)q1E0B_O24[U'],lOb ]~WjHޏTQ5Syu wq)xnw8~)c 쫬gٲߠ H% k5dƝk> kEj,0% b"vi2Wس_CuK)K{n|>t{P1򨾜j>'kEkƗBg*H%'_aY6Bn!TL&ɌOb{c`'d^{t\i^[uɐ[}q0lM˕G:‚4kb祔c^:?bpg… +37stH:0}en6x˟%/<]BL&* 5&fK9Mq)/iyqtA%kUe[ڛKN]Ě^,"`/ s[EQQm?|XJ߅92m]G.E΃ח U*Cn.j_)Tѧj̿30ڇ!A0=͜ar I3$C^-9#|pk!)?7.x9 @OO;WƝZBFU keZ75F6Tc6"ZȚs2y/1 ʵ:u4xa`C>6Rb/Yм)^=+~uRd`/|_8xbB0?Ft||Z\##|K 0>>zxv8۴吅q 8ĥ)"6>~\8:qM}#͚'ĉ#p\׶ l#bA?)|g g9|8jP(cr,BwV (WliVxxᡁ@0Okn;ɥh$_ckCgriv}>=wGzβ KkBɛ[˪ !J)h&k2%07δt}!d<9;I&0wV/ v 0<H}L&8ob%Hi|޶o&h1L|u֦y~󛱢8fٲUsւ)0oiFx2}X[zVYr_;N(w]_4B@OanC?gĦx>мgx>ΛToZoOMp>40>V Oy V9iq!4 LN,ˢu{jsz]|"R޻&'ƚ{53ўFu(<٪9:΋]B;)B>1::8;~)Yt|0(pw2N%&X,URBK)3\zz&}ax4;ǟ(tLNg{N|Ǽ\G#C9g$^\}p?556]/RP.90 k,U8/u776s ʪ_01چ|\N 0VV*3H鴃J7iI!wG_^ypl}r*jɤSR 5QN@ iZ#1ٰy;_\3\BQQ x:WJv츟ٯ$"@6 S#qe딇(/P( Dy~TOϻ<4:-+F`0||;Xl-"uw$Цi󼕝mKʩorz"mϺ$F:~E'ҐvD\y?Rr8_He@ e~O,T.(ފR*cY^m|cVR[8 JҡSm!ΆԨb)RHG{?MpqrmN>߶Y)\p,d#xۆWY*,l6]v0h15M˙MS8+EdI='LBJIH7_9{Caз*Lq,dt >+~ّeʏ?xԕ4bBAŚjﵫ!'\Ը$WNvKO}ӽmSşذqsOy?\[,d@'73'j%kOe`1.g2"e =YIzS2|zŐƄa\U,dP;jhhhaxǶ?КZ՚.q SE+XrbOu%\GتX(H,N^~]JyEZQKceTQ]VGYqnah;y$cQahT&QPZ*iZ8UQQM.qo/T\7X"u?Mttl2Xq(IoW{R^ ux*SYJ! 4S.Jy~ BROS[V|žKNɛP(L6V^|cR7i7nZW1Fd@ Ara{詑|(T*dN]Ko?s=@ |_EvF]׍kR)eBJc" MUUbY6`~V޴dJKß&~'d3i WWWWWW
Current Directory: /opt/saltstack/salt/lib/python3.10/site-packages/salt/engines
Viewing File: /opt/saltstack/salt/lib/python3.10/site-packages/salt/engines/libvirt_events.py
""" An engine that listens for libvirt events and resends them to the salt event bus. The minimal configuration is the following and will listen to all events on the local hypervisor and send them with a tag starting with ``salt/engines/libvirt_events``: .. code-block:: yaml engines: - libvirt_events Note that the automatically-picked libvirt connection will depend on the value of ``uri_default`` in ``/etc/libvirt/libvirt.conf``. To force using another connection like the local LXC libvirt driver, set the ``uri`` property as in the following example configuration. .. code-block:: yaml engines: - libvirt_events: uri: lxc:/// tag_prefix: libvirt filters: - domain/lifecycle - domain/reboot - pool Filters is a list of event types to relay to the event bus. Items in this list can be either one of the main types (``domain``, ``network``, ``pool``, ``nodedev``, ``secret``), ``all`` or a more precise filter. These can be done with values like <main_type>/<subtype>. The possible values are in the CALLBACK_DEFS constant. If the filters list contains ``all``, all events will be relayed. Be aware that the list of events increases with libvirt versions, for example network events have been added in libvirt 1.2.1 and storage events in 2.0.0. Running the engine on non-root ------------------------------ Running this engine as non-root requires a special attention, which is surely the case for the master running as user `salt`. The engine is likely to fail to connect to libvirt with an error like this one: [ERROR ] authentication unavailable: no polkit agent available to authenticate action 'org.libvirt.unix.monitor' To fix this, the user running the engine, for example the salt-master, needs to have the rights to connect to libvirt in the machine polkit config. A polkit rule like the following one will allow `salt` user to connect to libvirt: .. code-block:: javascript polkit.addRule(function(action, subject) { if (action.id.indexOf("org.libvirt") == 0 && subject.user == "salt") { return polkit.Result.YES; } }); :depends: libvirt 1.0.0+ python binding .. versionadded:: 2019.2.0 """ import logging import urllib.parse import salt.utils.event log = logging.getLogger(__name__) try: import libvirt except ImportError: libvirt = None # pylint: disable=invalid-name def __virtual__(): """ Only load if libvirt python binding is present """ if libvirt is None: msg = "libvirt module not found" elif libvirt.getVersion() < 1000000: msg = "libvirt >= 1.0.0 required" else: msg = "" return not bool(msg), msg REGISTER_FUNCTIONS = { "domain": "domainEventRegisterAny", "network": "networkEventRegisterAny", "pool": "storagePoolEventRegisterAny", "nodedev": "nodeDeviceEventRegisterAny", "secret": "secretEventRegisterAny", } # Handle either BLOCK_JOB or BLOCK_JOB_2, but prefer the latter if hasattr(libvirt, "VIR_DOMAIN_EVENT_ID_BLOCK_JOB_2"): BLOCK_JOB_ID = "VIR_DOMAIN_EVENT_ID_BLOCK_JOB_2" else: BLOCK_JOB_ID = "VIR_DOMAIN_EVENT_ID_BLOCK_JOB" CALLBACK_DEFS = { "domain": ( ("lifecycle", None), ("reboot", None), ("rtc_change", None), ("watchdog", None), ("graphics", None), ("io_error", "VIR_DOMAIN_EVENT_ID_IO_ERROR_REASON"), ("control_error", None), ("disk_change", None), ("tray_change", None), ("pmwakeup", None), ("pmsuspend", None), ("balloon_change", None), ("pmsuspend_disk", None), ("device_removed", None), ("block_job", BLOCK_JOB_ID), ("tunable", None), ("agent_lifecycle", None), ("device_added", None), ("migration_iteration", None), ("job_completed", None), ("device_removal_failed", None), ("metadata_change", None), ("block_threshold", None), ), "network": (("lifecycle", None),), "pool": ( ("lifecycle", "VIR_STORAGE_POOL_EVENT_ID_LIFECYCLE"), ("refresh", "VIR_STORAGE_POOL_EVENT_ID_REFRESH"), ), "nodedev": ( ("lifecycle", "VIR_NODE_DEVICE_EVENT_ID_LIFECYCLE"), ("update", "VIR_NODE_DEVICE_EVENT_ID_UPDATE"), ), "secret": (("lifecycle", None), ("value_changed", None)), } def _compute_subprefix(attr): """ Get the part before the first '_' or the end of attr including the potential '_' """ return "".join((attr.split("_")[0], "_" if len(attr.split("_")) > 1 else "")) def _get_libvirt_enum_string(prefix, value): """ Convert the libvirt enum integer value into a human readable string. :param prefix: start of the libvirt attribute to look for. :param value: integer to convert to string """ attributes = [ attr[len(prefix) :] for attr in libvirt.__dict__ if attr.startswith(prefix) ] # Filter out the values starting with a common base as they match another enum prefixes = [_compute_subprefix(p) for p in attributes] counts = {p: prefixes.count(p) for p in prefixes} sub_prefixes = [ p for p, count in counts.items() if count > 1 or (p.endswith("_") and p[:-1] in prefixes) ] filtered = [ attr for attr in attributes if _compute_subprefix(attr) not in sub_prefixes ] for candidate in filtered: if value == getattr(libvirt, "".join((prefix, candidate))): name = candidate.lower().replace("_", " ") return name return "unknown" def _get_domain_event_detail(event, detail): """ Convert event and detail numeric values into a tuple of human readable strings """ event_name = _get_libvirt_enum_string("VIR_DOMAIN_EVENT_", event) if event_name == "unknown": return event_name, "unknown" prefix = f"VIR_DOMAIN_EVENT_{event_name.upper()}_" detail_name = _get_libvirt_enum_string(prefix, detail) return event_name, detail_name def _salt_send_event(opaque, conn, data): """ Convenience function adding common data to the event and sending it on the salt event bus. :param opaque: the opaque data that is passed to the callback. This is a dict with 'prefix', 'object' and 'event' keys. :param conn: libvirt connection :param data: additional event data dict to send """ tag_prefix = opaque["prefix"] object_type = opaque["object"] event_type = opaque["event"] # Prepare the connection URI to fit in the tag # qemu+ssh://user@host:1234/system -> qemu+ssh/user@host:1234/system uri = urllib.parse.urlparse(conn.getURI()) uri_tag = [uri.scheme] if uri.netloc: uri_tag.append(uri.netloc) path = uri.path.strip("/") if path: uri_tag.append(path) uri_str = "/".join(uri_tag) # Append some common data all_data = {"uri": conn.getURI()} all_data.update(data) tag = "/".join((tag_prefix, uri_str, object_type, event_type)) # Actually send the event in salt if __opts__.get("__role") == "master": salt.utils.event.get_master_event(__opts__, __opts__["sock_dir"]).fire_event( all_data, tag ) else: __salt__["event.send"](tag, all_data) def _salt_send_domain_event(opaque, conn, domain, event, event_data): """ Helper function send a salt event for a libvirt domain. :param opaque: the opaque data that is passed to the callback. This is a dict with 'prefix', 'object' and 'event' keys. :param conn: libvirt connection :param domain: name of the domain related to the event :param event: name of the event :param event_data: additional event data dict to send """ data = { "domain": { "name": domain.name(), "id": domain.ID(), "uuid": domain.UUIDString(), }, "event": event, } data.update(event_data) _salt_send_event(opaque, conn, data) def _domain_event_lifecycle_cb(conn, domain, event, detail, opaque): """ Domain lifecycle events handler """ event_str, detail_str = _get_domain_event_detail(event, detail) _salt_send_domain_event( opaque, conn, domain, opaque["event"], {"event": event_str, "detail": detail_str}, ) def _domain_event_reboot_cb(conn, domain, opaque): """ Domain reboot events handler """ _salt_send_domain_event(opaque, conn, domain, opaque["event"], {}) def _domain_event_rtc_change_cb(conn, domain, utcoffset, opaque): """ Domain RTC change events handler """ _salt_send_domain_event( opaque, conn, domain, opaque["event"], {"utcoffset": utcoffset} ) def _domain_event_watchdog_cb(conn, domain, action, opaque): """ Domain watchdog events handler """ _salt_send_domain_event( opaque, conn, domain, opaque["event"], {"action": _get_libvirt_enum_string("VIR_DOMAIN_EVENT_WATCHDOG_", action)}, ) def _domain_event_io_error_cb(conn, domain, srcpath, devalias, action, reason, opaque): """ Domain I/O Error events handler """ _salt_send_domain_event( opaque, conn, domain, opaque["event"], { "srcPath": srcpath, "dev": devalias, "action": _get_libvirt_enum_string("VIR_DOMAIN_EVENT_IO_ERROR_", action), "reason": reason, }, ) def _domain_event_graphics_cb( conn, domain, phase, local, remote, auth, subject, opaque ): """ Domain graphics events handler """ prefix = "VIR_DOMAIN_EVENT_GRAPHICS_" def get_address(addr): """ transform address structure into event data piece """ return { "family": _get_libvirt_enum_string(f"{prefix}_ADDRESS_", addr["family"]), "node": addr["node"], "service": addr["service"], } _salt_send_domain_event( opaque, conn, domain, opaque["event"], { "phase": _get_libvirt_enum_string(prefix, phase), "local": get_address(local), "remote": get_address(remote), "authScheme": auth, "subject": [{"type": item[0], "name": item[1]} for item in subject], }, ) def _domain_event_control_error_cb(conn, domain, opaque): """ Domain control error events handler """ _salt_send_domain_event(opaque, conn, domain, opaque["event"], {}) def _domain_event_disk_change_cb(conn, domain, old_src, new_src, dev, reason, opaque): """ Domain disk change events handler """ _salt_send_domain_event( opaque, conn, domain, opaque["event"], { "oldSrcPath": old_src, "newSrcPath": new_src, "dev": dev, "reason": _get_libvirt_enum_string("VIR_DOMAIN_EVENT_DISK_", reason), }, ) def _domain_event_tray_change_cb(conn, domain, dev, reason, opaque): """ Domain tray change events handler """ _salt_send_domain_event( opaque, conn, domain, opaque["event"], { "dev": dev, "reason": _get_libvirt_enum_string("VIR_DOMAIN_EVENT_TRAY_CHANGE_", reason), }, ) def _domain_event_pmwakeup_cb(conn, domain, reason, opaque): """ Domain wakeup events handler """ _salt_send_domain_event( opaque, conn, domain, opaque["event"], {"reason": "unknown"} # currently unused ) def _domain_event_pmsuspend_cb(conn, domain, reason, opaque): """ Domain suspend events handler """ _salt_send_domain_event( opaque, conn, domain, opaque["event"], {"reason": "unknown"} # currently unused ) def _domain_event_balloon_change_cb(conn, domain, actual, opaque): """ Domain balloon change events handler """ _salt_send_domain_event(opaque, conn, domain, opaque["event"], {"actual": actual}) def _domain_event_pmsuspend_disk_cb(conn, domain, reason, opaque): """ Domain disk suspend events handler """ _salt_send_domain_event( opaque, conn, domain, opaque["event"], {"reason": "unknown"} # currently unused ) def _domain_event_block_job_cb(conn, domain, disk, job_type, status, opaque): """ Domain block job events handler """ _salt_send_domain_event( opaque, conn, domain, opaque["event"], { "disk": disk, "type": _get_libvirt_enum_string("VIR_DOMAIN_BLOCK_JOB_TYPE_", job_type), "status": _get_libvirt_enum_string("VIR_DOMAIN_BLOCK_JOB_", status), }, ) def _domain_event_device_removed_cb(conn, domain, dev, opaque): """ Domain device removal events handler """ _salt_send_domain_event(opaque, conn, domain, opaque["event"], {"dev": dev}) def _domain_event_tunable_cb(conn, domain, params, opaque): """ Domain tunable events handler """ _salt_send_domain_event(opaque, conn, domain, opaque["event"], {"params": params}) # pylint: disable=invalid-name def _domain_event_agent_lifecycle_cb(conn, domain, state, reason, opaque): """ Domain agent lifecycle events handler """ _salt_send_domain_event( opaque, conn, domain, opaque["event"], { "state": _get_libvirt_enum_string( "VIR_CONNECT_DOMAIN_EVENT_AGENT_LIFECYCLE_STATE_", state ), "reason": _get_libvirt_enum_string( "VIR_CONNECT_DOMAIN_EVENT_AGENT_LIFECYCLE_REASON_", reason ), }, ) def _domain_event_device_added_cb(conn, domain, dev, opaque): """ Domain device addition events handler """ _salt_send_domain_event(opaque, conn, domain, opaque["event"], {"dev": dev}) # pylint: disable=invalid-name def _domain_event_migration_iteration_cb(conn, domain, iteration, opaque): """ Domain migration iteration events handler """ _salt_send_domain_event( opaque, conn, domain, opaque["event"], {"iteration": iteration} ) def _domain_event_job_completed_cb(conn, domain, params, opaque): """ Domain job completion events handler """ _salt_send_domain_event(opaque, conn, domain, opaque["event"], {"params": params}) def _domain_event_device_removal_failed_cb(conn, domain, dev, opaque): """ Domain device removal failure events handler """ _salt_send_domain_event(opaque, conn, domain, opaque["event"], {"dev": dev}) def _domain_event_metadata_change_cb(conn, domain, mtype, nsuri, opaque): """ Domain metadata change events handler """ _salt_send_domain_event( opaque, conn, domain, opaque["event"], { "type": _get_libvirt_enum_string("VIR_DOMAIN_METADATA_", mtype), "nsuri": nsuri, }, ) def _domain_event_block_threshold_cb( conn, domain, dev, path, threshold, excess, opaque ): """ Domain block threshold events handler """ _salt_send_domain_event( opaque, conn, domain, opaque["event"], {"dev": dev, "path": path, "threshold": threshold, "excess": excess}, ) def _network_event_lifecycle_cb(conn, net, event, detail, opaque): """ Network lifecycle events handler """ _salt_send_event( opaque, conn, { "network": {"name": net.name(), "uuid": net.UUIDString()}, "event": _get_libvirt_enum_string("VIR_NETWORK_EVENT_", event), "detail": "unknown", # currently unused }, ) def _pool_event_lifecycle_cb(conn, pool, event, detail, opaque): """ Storage pool lifecycle events handler """ _salt_send_event( opaque, conn, { "pool": {"name": pool.name(), "uuid": pool.UUIDString()}, "event": _get_libvirt_enum_string("VIR_STORAGE_POOL_EVENT_", event), "detail": "unknown", # currently unused }, ) def _pool_event_refresh_cb(conn, pool, opaque): """ Storage pool refresh events handler """ _salt_send_event( opaque, conn, { "pool": {"name": pool.name(), "uuid": pool.UUIDString()}, "event": opaque["event"], }, ) def _nodedev_event_lifecycle_cb(conn, dev, event, detail, opaque): """ Node device lifecycle events handler """ _salt_send_event( opaque, conn, { "nodedev": {"name": dev.name()}, "event": _get_libvirt_enum_string("VIR_NODE_DEVICE_EVENT_", event), "detail": "unknown", # currently unused }, ) def _nodedev_event_update_cb(conn, dev, opaque): """ Node device update events handler """ _salt_send_event( opaque, conn, {"nodedev": {"name": dev.name()}, "event": opaque["event"]} ) def _secret_event_lifecycle_cb(conn, secret, event, detail, opaque): """ Secret lifecycle events handler """ _salt_send_event( opaque, conn, { "secret": {"uuid": secret.UUIDString()}, "event": _get_libvirt_enum_string("VIR_SECRET_EVENT_", event), "detail": "unknown", # currently unused }, ) def _secret_event_value_changed_cb(conn, secret, opaque): """ Secret value change events handler """ _salt_send_event( opaque, conn, {"secret": {"uuid": secret.UUIDString()}, "event": opaque["event"]}, ) def _cleanup(cnx): """ Close the libvirt connection :param cnx: libvirt connection """ log.debug("Closing libvirt connection: %s", cnx.getURI()) cnx.close() def _callbacks_cleanup(cnx, callback_ids): """ Unregister all the registered callbacks :param cnx: libvirt connection :param callback_ids: dictionary mapping a libvirt object type to an ID list of callbacks to deregister """ for obj, ids in callback_ids.items(): register_name = REGISTER_FUNCTIONS[obj] deregister_name = register_name.replace("Reg", "Dereg") deregister = getattr(cnx, deregister_name) for callback_id in ids: deregister(callback_id) def _register_callback(cnx, tag_prefix, obj, event, real_id): """ Helper function registering a callback :param cnx: libvirt connection :param tag_prefix: salt event tag prefix to use :param obj: the libvirt object name for the event. Needs to be one of the REGISTER_FUNCTIONS keys. :param event: the event type name. :param real_id: the libvirt name of an alternative event id to use or None :rtype integer value needed to deregister the callback """ libvirt_name = real_id if real_id is None: libvirt_name = f"VIR_{obj}_EVENT_ID_{event}".upper() if not hasattr(libvirt, libvirt_name): log.warning('Skipping "%s/%s" events: libvirt too old', obj, event) return None libvirt_id = getattr(libvirt, libvirt_name) callback_name = f"_{obj}_event_{event}_cb" callback = globals().get(callback_name, None) if callback is None: log.error("Missing function %s in engine", callback_name) return None register = getattr(cnx, REGISTER_FUNCTIONS[obj]) return register( None, libvirt_id, callback, {"prefix": tag_prefix, "object": obj, "event": event}, ) def _append_callback_id(ids, obj, callback_id): """ Helper function adding a callback ID to the IDs dict. The callback ids dict maps an object to event callback ids. :param ids: dict of callback IDs to update :param obj: one of the keys of REGISTER_FUNCTIONS :param callback_id: the result of _register_callback """ if obj not in ids: ids[obj] = [] ids[obj].append(callback_id) def start(uri=None, tag_prefix="salt/engines/libvirt_events", filters=None): """ Listen to libvirt events and forward them to salt. :param uri: libvirt URI to listen on. Defaults to None to pick the first available local hypervisor :param tag_prefix: the beginning of the salt event tag to use. Defaults to 'salt/engines/libvirt_events' :param filters: the list of event of listen on. Defaults to 'all' """ if filters is None: filters = ["all"] try: libvirt.virEventRegisterDefaultImpl() cnx = libvirt.openReadOnly(uri) log.debug("Opened libvirt uri: %s", cnx.getURI()) callback_ids = {} all_filters = "all" in filters for obj, event_defs in CALLBACK_DEFS.items(): for event, real_id in event_defs: event_filter = "/".join((obj, event)) if ( event_filter not in filters and obj not in filters and not all_filters ): continue registered_id = _register_callback(cnx, tag_prefix, obj, event, real_id) if registered_id: _append_callback_id(callback_ids, obj, registered_id) exit_loop = False while not exit_loop: exit_loop = libvirt.virEventRunDefaultImpl() < 0 except Exception as err: # pylint: disable=broad-except log.exception(err) finally: _callbacks_cleanup(cnx, callback_ids) _cleanup(cnx)