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/runners
Viewing File: /opt/saltstack/salt/lib/python3.10/site-packages/salt/runners/virt.py
""" Control virtual machines via Salt """ import logging import os.path import salt.client import salt.key import salt.utils.cloud import salt.utils.files import salt.utils.stringutils from salt.exceptions import SaltClientError log = logging.getLogger(__name__) def _determine_host(data, omit=""): """ Determine what the most resource free host is based on the given data """ # This is just checking for the host with the most free ram, this needs # to be much more complicated. host = "" bestmem = 0 for hv_, comps in data.items(): if hv_ == omit: continue if not isinstance(comps, dict): continue if comps.get("freemem", 0) > bestmem: bestmem = comps["freemem"] host = hv_ return host def _find_vm(name, data, quiet=False): """ Scan the query data for the named VM """ for hv_ in data: # Check if data is a dict, and not '"virt.full_info" is not available.' if not isinstance(data[hv_], dict): continue if name in data[hv_].get("vm_info", {}): ret = {hv_: {name: data[hv_]["vm_info"][name]}} if not quiet: __jid_event__.fire_event( {"data": ret, "outputter": "nested"}, "progress" ) return ret return {} def query(host=None, quiet=False): """ Query the virtual machines. When called without options all hosts are detected and a full query is returned. A single host can be passed in to specify an individual host to query. """ if quiet: log.warning("'quiet' is deprecated. Please migrate to --quiet") ret = {} with salt.client.get_local_client(__opts__["conf_file"]) as client: try: for info in client.cmd_iter( "virtual:physical", "virt.full_info", tgt_type="grain" ): if not info: continue if not isinstance(info, dict): continue chunk = {} id_ = next(iter(info.keys())) if host: if host != id_: continue if not isinstance(info[id_], dict): continue if "ret" not in info[id_]: continue if not isinstance(info[id_]["ret"], dict): continue chunk[id_] = info[id_]["ret"] ret.update(chunk) if not quiet: __jid_event__.fire_event( {"data": chunk, "outputter": "virt_query"}, "progress" ) except SaltClientError as client_error: print(client_error) return ret def list(host=None, quiet=False, hyper=None): # pylint: disable=redefined-builtin """ List the virtual machines on each host, this is a simplified query, showing only the virtual machine names belonging to each host. A single host can be passed in to specify an individual host to list. """ if quiet: log.warning("'quiet' is deprecated. Please migrate to --quiet") ret = {} with salt.client.get_local_client(__opts__["conf_file"]) as client: for info in client.cmd_iter( "virtual:physical", "virt.vm_info", tgt_type="grain" ): if not info: continue if not isinstance(info, dict): continue chunk = {} id_ = next(iter(info.keys())) if host: if host != id_: continue if not isinstance(info[id_], dict): continue if "ret" not in info[id_]: continue if not isinstance(info[id_]["ret"], dict): continue data = {} for key, val in info[id_]["ret"].items(): if val["state"] in data: data[val["state"]].append(key) else: data[val["state"]] = [key] chunk[id_] = data ret.update(chunk) if not quiet: __jid_event__.fire_event( {"data": chunk, "outputter": "nested"}, "progress" ) return ret def next_host(): """ Return the host to use for the next autodeployed VM. This queries the available host and executes some math the determine the most "available" next host. """ host = _determine_host(query(quiet=True)) print(host) return host def host_info(host=None): """ Return information about the host connected to this master """ data = query(host, quiet=True) for id_ in data: if "vm_info" in data[id_]: data[id_].pop("vm_info") __jid_event__.fire_event({"data": data, "outputter": "nested"}, "progress") return data def init( name, cpu, mem, image, hypervisor="kvm", host=None, seed=True, nic="default", install=True, start=True, disk="default", saltenv="base", enable_vnc=False, seed_cmd="seed.apply", enable_qcow=False, serial_type="None", ): """ This routine is used to create a new virtual machine. This routines takes a number of options to determine what the newly created virtual machine will look like. name The mandatory name of the new virtual machine. The name option is also the minion id, all minions must have an id. cpu The number of cpus to allocate to this new virtual machine. mem The amount of memory to allocate to this virtual machine. The number is interpreted in megabytes. image The network location of the virtual machine image, commonly a location on the salt fileserver, but http, https and ftp can also be used. hypervisor The hypervisor to use for the new virtual machine. Default is `kvm`. host The host to use for the new virtual machine, if this is omitted Salt will automatically detect what host to use. seed Set to `False` to prevent Salt from seeding the new virtual machine. nic The nic profile to use, defaults to the "default" nic profile which assumes a single network interface per VM associated with the "br0" bridge on the master. install Set to False to prevent Salt from installing a minion on the new VM before it spins up. disk The disk profile to use saltenv The Salt environment to use enable_vnc Whether a VNC screen is attached to resulting VM. Default is `False`. seed_cmd If seed is `True`, use this execution module function to seed new VM. Default is `seed.apply`. enable_qcow Clone disk image as a copy-on-write qcow2 image, using downloaded `image` as backing file. serial_type Enable serial console. Set to 'pty' for serial console or 'tcp' for telnet. Default is 'None' """ __jid_event__.fire_event({"message": "Searching for hosts"}, "progress") data = query(host, quiet=True) # Check if the name is already deployed for node in data: if "vm_info" in data[node]: if name in data[node]["vm_info"]: __jid_event__.fire_event( {"message": f"Virtual machine {name} is already deployed"}, "progress", ) return "fail" if host is None: host = _determine_host(data) if host not in data or not host: __jid_event__.fire_event({"message": f"Host {host} was not found"}, "progress") return "fail" pub_key = None priv_key = None if seed: __jid_event__.fire_event({"message": "Minion will be preseeded"}, "progress") priv_key, pub_key = salt.utils.cloud.gen_keys() accepted_key = os.path.join(__opts__["pki_dir"], "minions", name) with salt.utils.files.fopen(accepted_key, "w") as fp_: fp_.write(salt.utils.stringutils.to_str(pub_key)) with salt.client.get_local_client(__opts__["conf_file"]) as client: __jid_event__.fire_event( {"message": f"Creating VM {name} on host {host}"}, "progress" ) try: cmd_ret = client.cmd_iter( host, "virt.init", [name, cpu, mem], timeout=600, kwarg={ "image": image, "nic": nic, "hypervisor": hypervisor, "start": start, "disk": disk, "saltenv": saltenv, "seed": seed, "install": install, "pub_key": pub_key, "priv_key": priv_key, "seed_cmd": seed_cmd, "enable_vnc": enable_vnc, "enable_qcow": enable_qcow, "serial_type": serial_type, }, ) except SaltClientError as client_error: # Fall through to ret error handling below print(client_error) ret = next(cmd_ret) if not ret: __jid_event__.fire_event( {"message": f"VM {name} was not initialized."}, "progress" ) return "fail" for minion_id in ret: if ret[minion_id]["ret"] is False: print( "VM {} initialization failed. Returned error: {}".format( name, ret[minion_id]["ret"] ) ) return "fail" __jid_event__.fire_event( {"message": f"VM {name} initialized on host {host}"}, "progress" ) return "good" def vm_info(name, quiet=False): """ Return the information on the named VM """ data = query(quiet=True) return _find_vm(name, data, quiet) def reset(name): """ Force power down and restart an existing VM """ ret = {} with salt.client.get_local_client(__opts__["conf_file"]) as client: data = vm_info(name, quiet=True) if not data: __jid_event__.fire_event( {"message": f"Failed to find VM {name} to reset"}, "progress" ) return "fail" host = next(iter(data.keys())) try: cmd_ret = client.cmd_iter(host, "virt.reset", [name], timeout=600) for comp in cmd_ret: ret.update(comp) __jid_event__.fire_event({"message": f"Reset VM {name}"}, "progress") except SaltClientError as client_error: print(client_error) return ret def start(name): """ Start a named virtual machine """ ret = {} with salt.client.get_local_client(__opts__["conf_file"]) as client: data = vm_info(name, quiet=True) if not data: __jid_event__.fire_event( {"message": f"Failed to find VM {name} to start"}, "progress" ) return "fail" host = next(iter(data.keys())) if data[host][name]["state"] == "running": print(f"VM {name} is already running") return "bad state" try: cmd_ret = client.cmd_iter(host, "virt.start", [name], timeout=600) except SaltClientError as client_error: return f"Virtual machine {name} not started: {client_error}" for comp in cmd_ret: ret.update(comp) __jid_event__.fire_event({"message": f"Started VM {name}"}, "progress") return "good" def force_off(name): """ Force power down the named virtual machine """ ret = {} with salt.client.get_local_client(__opts__["conf_file"]) as client: data = vm_info(name, quiet=True) if not data: print(f"Failed to find VM {name} to destroy") return "fail" host = next(iter(data.keys())) if data[host][name]["state"] == "shutdown": print(f"VM {name} is already shutdown") return "bad state" try: cmd_ret = client.cmd_iter(host, "virt.stop", [name], timeout=600) except SaltClientError as client_error: return "Virtual machine {} could not be forced off: {}".format( name, client_error ) for comp in cmd_ret: ret.update(comp) __jid_event__.fire_event({"message": f"Powered off VM {name}"}, "progress") return "good" def purge(name, delete_key=True): """ Destroy the named VM """ ret = {} with salt.client.get_local_client(__opts__["conf_file"]) as client: data = vm_info(name, quiet=True) if not data: __jid_event__.fire_event( {"error": f"Failed to find VM {name} to purge"}, "progress" ) return "fail" host = next(iter(data.keys())) try: cmd_ret = client.cmd_iter(host, "virt.purge", [name, True], timeout=600) except SaltClientError as client_error: return "Virtual machine {} could not be purged: {}".format( name, client_error ) for comp in cmd_ret: ret.update(comp) if delete_key: log.debug("Deleting key %s", name) with salt.key.Key(__opts__) as skey: skey.delete_key(name) __jid_event__.fire_event({"message": f"Purged VM {name}"}, "progress") return "good" def pause(name): """ Pause the named VM """ ret = {} with salt.client.get_local_client(__opts__["conf_file"]) as client: data = vm_info(name, quiet=True) if not data: __jid_event__.fire_event( {"error": f"Failed to find VM {name} to pause"}, "progress" ) return "fail" host = next(iter(data.keys())) if data[host][name]["state"] == "paused": __jid_event__.fire_event( {"error": f"VM {name} is already paused"}, "progress" ) return "bad state" try: cmd_ret = client.cmd_iter(host, "virt.pause", [name], timeout=600) except SaltClientError as client_error: return "Virtual machine {} could not be pasued: {}".format( name, client_error ) for comp in cmd_ret: ret.update(comp) __jid_event__.fire_event({"message": f"Paused VM {name}"}, "progress") return "good" def resume(name): """ Resume a paused VM """ ret = {} with salt.client.get_local_client(__opts__["conf_file"]) as client: data = vm_info(name, quiet=True) if not data: __jid_event__.fire_event( {"error": f"Failed to find VM {name} to pause"}, "progress" ) return "not found" host = next(iter(data.keys())) if data[host][name]["state"] != "paused": __jid_event__.fire_event({"error": f"VM {name} is not paused"}, "progress") return "bad state" try: cmd_ret = client.cmd_iter(host, "virt.resume", [name], timeout=600) except SaltClientError as client_error: return "Virtual machine {} could not be resumed: {}".format( name, client_error ) for comp in cmd_ret: ret.update(comp) __jid_event__.fire_event({"message": f"Resumed VM {name}"}, "progress") return "good" def migrate(name, target=""): """ Migrate a VM from one host to another. This routine will just start the migration and display information on how to look up the progress. """ with salt.client.get_local_client(__opts__["conf_file"]) as client: data = query(quiet=True) origin_data = _find_vm(name, data, quiet=True) try: origin_host = next(iter(origin_data)) except StopIteration: __jid_event__.fire_event( {"error": f"Named VM {name} was not found to migrate"}, "progress", ) return "" disks = origin_data[origin_host][name]["disks"] if not origin_data: __jid_event__.fire_event( {"error": f"Named VM {name} was not found to migrate"}, "progress", ) return "" if not target: target = _determine_host(data, origin_host) if target not in data: __jid_event__.fire_event( {"error": f"Target host {origin_data} not found"}, "progress" ) return "" try: client.cmd(target, "virt.seed_non_shared_migrate", [disks, True]) jid = client.cmd_async( origin_host, "virt.migrate", [name, target], copy_storage=all ) except SaltClientError as client_error: return "Virtual machine {} could not be migrated: {}".format( name, client_error ) msg = ( "The migration of virtual machine {} to host {} has begun, " "and can be tracked via jid {}. The ``salt-run virt.query`` " "runner can also be used, the target VM will be shown as paused " "until the migration is complete.".format(name, target, jid) ) __jid_event__.fire_event({"message": msg}, "progress")