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/states
Viewing File: /opt/saltstack/salt/lib/python3.10/site-packages/salt/states/esxcluster.py
""" Manage VMware ESXi Clusters. .. Warning:: This module will be deprecated in a future release of Salt. VMware strongly recommends using the `VMware Salt extensions <https://docs.saltproject.io/salt/extensions/salt-ext-modules-vmware/en/latest/all.html>`_ instead of the ESX cluster module. Because the Salt extensions are newer and actively supported by VMware, they are more compatible with current versions of ESXi and they work well with the latest features in the VMware product line. Dependencies ============ - pyVmomi Python Module pyVmomi ------- PyVmomi can be installed via pip: .. code-block:: bash pip install pyVmomi .. note:: Version 6.0 of pyVmomi has some problems with SSL error handling on certain versions of Python. If using version 6.0 of pyVmomi, Python 2.7.9, or newer must be present. This is due to an upstream dependency in pyVmomi 6.0 that is not supported in Python versions 2.7 to 2.7.8. If the version of Python is not in the supported range, you will need to install an earlier version of pyVmomi. See `Issue #29537`_ for more information. .. _Issue #29537: https://github.com/saltstack/salt/issues/29537 Based on the note above, to install an earlier version of pyVmomi than the version currently listed in PyPi, run the following: .. code-block:: bash pip install pyVmomi==5.5.0.2014.1.1 The 5.5.0.2014.1.1 is a known stable version that this original ESXi State Module was developed against. """ import logging import sys from functools import wraps import salt.exceptions from salt.config.schemas.esxcluster import ESXClusterConfigSchema, LicenseSchema from salt.utils import dictupdate from salt.utils.dictdiffer import recursive_diff from salt.utils.listdiffer import list_diff # External libraries try: import jsonschema HAS_JSONSCHEMA = True except ImportError: HAS_JSONSCHEMA = False try: from pyVmomi import VmomiSupport HAS_PYVMOMI = True except ImportError: HAS_PYVMOMI = False # Get Logging Started log = logging.getLogger(__name__) def __virtual__(): if not HAS_JSONSCHEMA: return False, "State module did not load: jsonschema not found" if not HAS_PYVMOMI: return False, "State module did not load: pyVmomi not found" # We check the supported vim versions to infer the pyVmomi version if ( "vim25/6.0" in VmomiSupport.versionMap and sys.version_info > (2, 7) and sys.version_info < (2, 7, 9) ): return ( False, "State module did not load: Incompatible versions of Python and pyVmomi" " present. See Issue #29537.", ) return True def _deprecation_message(function): """ Decorator wrapper to warn about azurearm deprecation """ @wraps(function) def wrapped(*args, **kwargs): salt.utils.versions.warn_until( "Argon", "The 'esxcluster' functionality in Salt has been deprecated and its " "functionality will be removed in version 3008 in favor of the " "saltext.vmware Salt Extension. " "(https://github.com/saltstack/salt-ext-modules-vmware)", category=FutureWarning, ) ret = function(*args, **salt.utils.args.clean_kwargs(**kwargs)) return ret return wrapped @_deprecation_message def mod_init(low): """ Retrieves and adapt the login credentials from the proxy connection module """ return True def _get_vsan_datastore(si, cluster_name): """Retrieves the vsan_datastore""" log.trace("Retrieving vsan datastore") vsan_datastores = [ ds for ds in __salt__["vsphere.list_datastores_via_proxy"](service_instance=si) if ds["type"] == "vsan" ] if not vsan_datastores: raise salt.exceptions.VMwareObjectRetrievalError( f"No vSAN datastores where retrieved for cluster '{cluster_name}'" ) return vsan_datastores[0] @_deprecation_message def cluster_configured(name, cluster_config): """ Configures a cluster. Creates a new cluster, if it doesn't exist on the vCenter or reconfigures it if configured differently Supported proxies: esxdatacenter, esxcluster name Name of the state. If the state is run in by an ``esxdatacenter`` proxy, it will be the name of the cluster. cluster_config Configuration applied to the cluster. Complex datastructure following the ESXClusterConfigSchema. Valid example is: .. code-block:: yaml drs: default_vm_behavior: fullyAutomated enabled: true vmotion_rate: 3 ha: admission_control _enabled: false default_vm_settings: isolation_response: powerOff restart_priority: medium enabled: true hb_ds_candidate_policy: userSelectedDs host_monitoring: enabled options: - key: das.ignoreinsufficienthbdatastore value: 'true' vm_monitoring: vmMonitoringDisabled vm_swap_placement: vmDirectory vsan: auto_claim_storage: false compression_enabled: true dedup_enabled: true enabled: true """ proxy_type = __salt__["vsphere.get_proxy_type"]() if proxy_type == "esxdatacenter": cluster_name, datacenter_name = ( name, __salt__["esxdatacenter.get_details"]()["datacenter"], ) elif proxy_type == "esxcluster": cluster_name, datacenter_name = ( __salt__["esxcluster.get_details"]()["cluster"], __salt__["esxcluster.get_details"]()["datacenter"], ) else: raise salt.exceptions.CommandExecutionError(f"Unsupported proxy {proxy_type}") log.info( "Running %s for cluster '%s' in datacenter '%s'", name, cluster_name, datacenter_name, ) cluster_dict = cluster_config log.trace("cluster_dict = %s", cluster_dict) changes_required = False ret = {"name": name, "changes": {}, "result": None, "comment": "Default"} comments = [] changes = {} changes_required = False try: log.trace("Validating cluster_configured state input") schema = ESXClusterConfigSchema.serialize() log.trace("schema = %s", schema) try: jsonschema.validate(cluster_dict, schema) except jsonschema.exceptions.ValidationError as exc: raise salt.exceptions.InvalidESXClusterPayloadError(exc) current = None si = __salt__["vsphere.get_service_instance_via_proxy"]() try: current = __salt__["vsphere.list_cluster"]( datacenter_name, cluster_name, service_instance=si ) except salt.exceptions.VMwareObjectRetrievalError: changes_required = True if __opts__["test"]: comments.append( "State {} will create cluster '{}' in datacenter '{}'.".format( name, cluster_name, datacenter_name ) ) log.info(comments[-1]) __salt__["vsphere.disconnect"](si) ret.update({"result": None, "comment": "\n".join(comments)}) return ret log.trace( "Creating cluster '%s' in datacenter '%s'.", cluster_name, datacenter_name, ) __salt__["vsphere.create_cluster"]( cluster_dict, datacenter_name, cluster_name, service_instance=si ) comments.append( "Created cluster '{}' in datacenter '{}'".format( cluster_name, datacenter_name ) ) log.info(comments[-1]) changes.update({"new": cluster_dict}) if current: # Cluster already exists # We need to handle lists sepparately ldiff = None if "ha" in cluster_dict and "options" in cluster_dict["ha"]: ldiff = list_diff( current.get("ha", {}).get("options", []), cluster_dict.get("ha", {}).get("options", []), "key", ) log.trace("options diffs = %s", ldiff.diffs) # Remove options if exist del cluster_dict["ha"]["options"] if "ha" in current and "options" in current["ha"]: del current["ha"]["options"] diff = recursive_diff(current, cluster_dict) log.trace("diffs = %s", diff.diffs) if not (diff.diffs or (ldiff and ldiff.diffs)): # No differences comments.append( "Cluster '{}' in datacenter '{}' is up to date. Nothing to be done.".format( cluster_name, datacenter_name ) ) log.info(comments[-1]) else: changes_required = True changes_str = "" if diff.diffs: changes_str = f"{changes_str}{diff.changes_str}" if ldiff and ldiff.diffs: changes_str = "{}\nha:\n options:\n{}".format( changes_str, "\n".join([f" {l}" for l in ldiff.changes_str2.split("\n")]), ) # Apply the changes if __opts__["test"]: comments.append( "State {} will update cluster '{}' in datacenter '{}':\n{}".format( name, cluster_name, datacenter_name, changes_str ) ) else: new_values = diff.new_values old_values = diff.old_values if ldiff and ldiff.new_values: dictupdate.update( new_values, {"ha": {"options": ldiff.new_values}} ) if ldiff and ldiff.old_values: dictupdate.update( old_values, {"ha": {"options": ldiff.old_values}} ) log.trace("new_values = %s", new_values) __salt__["vsphere.update_cluster"]( new_values, datacenter_name, cluster_name, service_instance=si ) comments.append( "Updated cluster '{}' in datacenter '{}'".format( cluster_name, datacenter_name ) ) log.info(comments[-1]) changes.update({"new": new_values, "old": old_values}) __salt__["vsphere.disconnect"](si) ret_status = True if __opts__["test"] and changes_required: ret_status = None ret.update( {"result": ret_status, "comment": "\n".join(comments), "changes": changes} ) return ret except salt.exceptions.CommandExecutionError as exc: log.error("Error: %s", exc, exc_info=True) if si: __salt__["vsphere.disconnect"](si) ret.update({"result": False, "comment": str(exc)}) return ret @_deprecation_message def vsan_datastore_configured(name, datastore_name): """ Configures the cluster's VSAN datastore WARNING: The VSAN datastore is created automatically after the first ESXi host is added to the cluster; the state assumes that the datastore exists and errors if it doesn't. """ cluster_name, datacenter_name = ( __salt__["esxcluster.get_details"]()["cluster"], __salt__["esxcluster.get_details"]()["datacenter"], ) display_name = f"{datacenter_name}/{cluster_name}" log.info("Running vsan_datastore_configured for '%s'", display_name) ret = {"name": name, "changes": {}, "result": None, "comment": "Default"} comments = [] changes = {} changes_required = False try: si = __salt__["vsphere.get_service_instance_via_proxy"]() # Checking if we need to rename the vsan datastore vsan_ds = _get_vsan_datastore(si, cluster_name) if vsan_ds["name"] == datastore_name: comments.append( "vSAN datastore is correctly named '{}'. Nothing to be done.".format( vsan_ds["name"] ) ) log.info(comments[-1]) else: # vsan_ds needs to be updated changes_required = True if __opts__["test"]: comments.append( "State {} will rename the vSAN datastore to '{}'.".format( name, datastore_name ) ) log.info(comments[-1]) else: log.trace( "Renaming vSAN datastore '%s' to '%s'", vsan_ds["name"], datastore_name, ) __salt__["vsphere.rename_datastore"]( datastore_name=vsan_ds["name"], new_datastore_name=datastore_name, service_instance=si, ) comments.append(f"Renamed vSAN datastore to '{datastore_name}'.") changes = { "vsan_datastore": { "new": {"name": datastore_name}, "old": {"name": vsan_ds["name"]}, } } log.info(comments[-1]) __salt__["vsphere.disconnect"](si) ret.update( { "result": ( True if (not changes_required) else None if __opts__["test"] else True ), "comment": "\n".join(comments), "changes": changes, } ) return ret except salt.exceptions.CommandExecutionError as exc: log.error("Error: %s", exc, exc_info=True) if si: __salt__["vsphere.disconnect"](si) ret.update({"result": False, "comment": exc.strerror}) return ret @_deprecation_message def licenses_configured(name, licenses=None): """ Configures licenses on the cluster entity Checks if each license exists on the server: - if it doesn't, it creates it Check if license is assigned to the cluster: - if it's not assigned to the cluster: - assign it to the cluster if there is space - error if there's no space - if it's assigned to the cluster nothing needs to be done """ ret = {"name": name, "changes": {}, "result": None, "comment": "Default"} if not licenses: raise salt.exceptions.ArgumentValueError("No licenses provided") cluster_name, datacenter_name = ( __salt__["esxcluster.get_details"]()["cluster"], __salt__["esxcluster.get_details"]()["datacenter"], ) display_name = f"{datacenter_name}/{cluster_name}" log.info("Running licenses configured for '%s'", display_name) log.trace("licenses = %s", licenses) entity = {"type": "cluster", "datacenter": datacenter_name, "cluster": cluster_name} log.trace("entity = %s", entity) comments = [] changes = {} has_errors = False needs_changes = False try: # Validate licenses log.trace("Validating licenses") schema = LicenseSchema.serialize() try: jsonschema.validate({"licenses": licenses}, schema) except jsonschema.exceptions.ValidationError as exc: raise salt.exceptions.InvalidLicenseError(exc) si = __salt__["vsphere.get_service_instance_via_proxy"]() # Retrieve licenses existing_licenses = __salt__["vsphere.list_licenses"](service_instance=si) # Cycle through licenses for license_name, license in licenses.items(): # Check if license already exists filtered_licenses = [l for l in existing_licenses if l["key"] == license] # TODO Update license description - not of interest right now if not filtered_licenses: # License doesn't exist - add and assign to cluster needs_changes = True if __opts__["test"]: # If it doesn't exist it clearly needs to be assigned as # well so we can stop the check here comments.append( "State {} will add license '{}', and assign it to cluster '{}'.".format( name, license_name, display_name ) ) log.info(comments[-1]) continue else: try: existing_license = __salt__["vsphere.add_license"]( key=license, description=license_name, service_instance=si ) except salt.exceptions.VMwareApiError as ex: comments.append(ex.err_msg) log.error(comments[-1]) has_errors = True continue comments.append(f"Added license '{license_name}'.") log.info(comments[-1]) else: # License exists let's check if it's assigned to the cluster comments.append( "License '{}' already exists. Nothing to be done.".format( license_name ) ) log.info(comments[-1]) existing_license = filtered_licenses[0] log.trace("Checking licensed entities...") assigned_licenses = __salt__["vsphere.list_assigned_licenses"]( entity=entity, entity_display_name=display_name, service_instance=si ) # Checking if any of the licenses already assigned have the same # name as the new license; the already assigned license would be # replaced by the new license # # Licenses with different names but matching features would be # replaced as well, but searching for those would be very complex # # the name check if good enough for now already_assigned_license = ( assigned_licenses[0] if assigned_licenses else None ) if already_assigned_license and already_assigned_license["key"] == license: # License is already assigned to entity comments.append( "License '{}' already assigned to cluster '{}'. Nothing to be done.".format( license_name, display_name ) ) log.info(comments[-1]) continue needs_changes = True # License needs to be assigned to entity if existing_license["capacity"] <= existing_license["used"]: # License is already fully used comments.append( "Cannot assign license '{}' to cluster '{}'. No free capacity" " available.".format(license_name, display_name) ) log.error(comments[-1]) has_errors = True continue # Assign license if __opts__["test"]: comments.append( "State {} will assign license '{}' to cluster '{}'.".format( name, license_name, display_name ) ) log.info(comments[-1]) else: try: __salt__["vsphere.assign_license"]( license_key=license, license_name=license_name, entity=entity, entity_display_name=display_name, service_instance=si, ) except salt.exceptions.VMwareApiError as ex: comments.append(ex.err_msg) log.error(comments[-1]) has_errors = True continue comments.append( "Assigned license '{}' to cluster '{}'.".format( license_name, display_name ) ) log.info(comments[-1]) # Note: Because the already_assigned_license was retrieved # from the assignment license manager it doesn't have a used # value - that's a limitation from VMware. The license would # need to be retrieved again from the license manager to get # the value # Hide license keys assigned_license = __salt__["vsphere.list_assigned_licenses"]( entity=entity, entity_display_name=display_name, service_instance=si )[0] assigned_license["key"] = "<hidden>" if already_assigned_license: already_assigned_license["key"] = "<hidden>" if ( already_assigned_license and already_assigned_license["capacity"] == sys.maxsize ): already_assigned_license["capacity"] = "Unlimited" changes[license_name] = { "new": assigned_license, "old": already_assigned_license, } continue __salt__["vsphere.disconnect"](si) ret.update( { "result": ( True if (not needs_changes) else None if __opts__["test"] else False if has_errors else True ), "comment": "\n".join(comments), "changes": changes if not __opts__["test"] else {}, } ) return ret except salt.exceptions.CommandExecutionError as exc: log.error("Error: %s", exc, exc_info=True) if si: __salt__["vsphere.disconnect"](si) ret.update({"result": False, "comment": exc.strerror}) return ret