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/utils
Viewing File: /opt/saltstack/salt/lib/python3.10/site-packages/salt/utils/zfs.py
""" Utility functions for zfs These functions are for dealing with type conversion and basic execution :maintainer: Jorge Schrauwen <sjorge@blackdot.be> :maturity: new :depends: salt.utils.stringutils, salt.ext, salt.module.cmdmod :platform: illumos,freebsd,linux .. versionadded:: 2018.3.1 """ import logging import math import os import re from numbers import Number import salt.modules.cmdmod from salt.utils.decorators import memoize as real_memoize from salt.utils.odict import OrderedDict from salt.utils.stringutils import to_num as str_to_num # Size conversion data re_zfs_size = re.compile(r"^(\d+|\d+(?=\d*)\.\d+)([KkMmGgTtPpEe][Bb]?)$") zfs_size = ["K", "M", "G", "T", "P", "E"] log = logging.getLogger(__name__) def _check_retcode(cmd): """ Simple internal wrapper for cmdmod.retcode """ return ( salt.modules.cmdmod.retcode(cmd, output_loglevel="quiet", ignore_retcode=True) == 0 ) def _exec(**kwargs): """ Simple internal wrapper for cmdmod.run """ if "ignore_retcode" not in kwargs: kwargs["ignore_retcode"] = True if "output_loglevel" not in kwargs: kwargs["output_loglevel"] = "quiet" return salt.modules.cmdmod.run_all(**kwargs) def _merge_last(values, merge_after, merge_with=" "): """ Merge values all values after X into the last value """ if len(values) > merge_after: values = values[0 : (merge_after - 1)] + [ merge_with.join(values[(merge_after - 1) :]) ] return values def _property_normalize_name(name): """ Normalizes property names """ if "@" in name: name = name[: name.index("@") + 1] return name def _property_detect_type(name, values): """ Detect the datatype of a property """ value_type = "str" if values.startswith("on | off"): value_type = "bool" elif values.startswith("yes | no"): value_type = "bool_alt" elif values in ["<size>", "<size> | none"]: value_type = "size" elif values in ["<count>", "<count> | none", "<guid>"]: value_type = "numeric" elif name in ["sharenfs", "sharesmb", "canmount"]: value_type = "bool" elif name in ["version", "copies"]: value_type = "numeric" return value_type def _property_create_dict(header, data): """ Create a property dict """ prop = dict(zip(header, _merge_last(data, len(header)))) prop["name"] = _property_normalize_name(prop["property"]) prop["type"] = _property_detect_type(prop["name"], prop["values"]) prop["edit"] = from_bool(prop["edit"]) if "inherit" in prop: prop["inherit"] = from_bool(prop["inherit"]) del prop["property"] return prop def _property_parse_cmd(cmd, alias=None): """ Parse output of zpool/zfs get command """ if not alias: alias = {} properties = {} # NOTE: append get to command if cmd[-3:] != "get": cmd += " get" # NOTE: parse output prop_hdr = [] for prop_data in _exec(cmd=cmd)["stderr"].split("\n"): # NOTE: make the line data more manageable prop_data = prop_data.lower().split() # NOTE: skip empty lines if not prop_data: continue # NOTE: parse header elif prop_data[0] == "property": prop_hdr = prop_data continue # NOTE: skip lines after data elif not prop_hdr or prop_data[1] not in ["no", "yes"]: continue # NOTE: create property dict prop = _property_create_dict(prop_hdr, prop_data) # NOTE: add property to dict properties[prop["name"]] = prop if prop["name"] in alias: properties[alias[prop["name"]]] = prop # NOTE: cleanup some duplicate data del prop["name"] return properties def _auto(direction, name, value, source="auto", convert_to_human=True): """ Internal magic for from_auto and to_auto """ # NOTE: check direction if direction not in ["to", "from"]: return value # NOTE: collect property data props = property_data_zpool() if source == "zfs": props = property_data_zfs() elif source == "auto": props.update(property_data_zfs()) # NOTE: figure out the conversion type value_type = props[name]["type"] if name in props else "str" # NOTE: convert if value_type == "size" and direction == "to": return globals()[f"{direction}_{value_type}"](value, convert_to_human) return globals()[f"{direction}_{value_type}"](value) @real_memoize def _zfs_cmd(): """ Return the path of the zfs binary if present """ # Get the path to the zfs binary. return salt.utils.path.which("zfs") @real_memoize def _zpool_cmd(): """ Return the path of the zpool binary if present """ # Get the path to the zfs binary. return salt.utils.path.which("zpool") def _command( source, command, flags=None, opts=None, property_name=None, property_value=None, filesystem_properties=None, pool_properties=None, target=None, ): """ Build and properly escape a zfs command .. note:: Input is not considered safe and will be passed through to_auto(from_auto('input_here')), you do not need to do so your self first. """ # NOTE: start with the zfs binary and command cmd = [_zpool_cmd() if source == "zpool" else _zfs_cmd(), command] # NOTE: append flags if we have any if flags is None: flags = [] for flag in flags: cmd.append(flag) # NOTE: append options # we pass through 'sorted' to guarantee the same order if opts is None: opts = {} for opt in sorted(opts): if not isinstance(opts[opt], list): opts[opt] = [opts[opt]] for val in opts[opt]: cmd.append(opt) cmd.append(to_str(val)) # NOTE: append filesystem properties (really just options with a key/value) # we pass through 'sorted' to guarantee the same order if filesystem_properties is None: filesystem_properties = {} for fsopt in sorted(filesystem_properties): cmd.append("-O" if source == "zpool" else "-o") cmd.append( "{key}={val}".format( key=fsopt, val=to_auto( fsopt, filesystem_properties[fsopt], source="zfs", convert_to_human=False, ), ) ) # NOTE: append pool properties (really just options with a key/value) # we pass through 'sorted' to guarantee the same order if pool_properties is None: pool_properties = {} for fsopt in sorted(pool_properties): cmd.append("-o") cmd.append( "{key}={val}".format( key=fsopt, val=to_auto( fsopt, pool_properties[fsopt], source="zpool", convert_to_human=False, ), ) ) # NOTE: append property and value # the set command takes a key=value pair, we need to support this if property_name is not None: if property_value is not None: if not isinstance(property_name, list): property_name = [property_name] if not isinstance(property_value, list): property_value = [property_value] for key, val in zip(property_name, property_value): cmd.append( "{key}={val}".format( key=key, val=to_auto(key, val, source=source, convert_to_human=False), ) ) else: cmd.append(property_name) # NOTE: append the target(s) if target is not None: if not isinstance(target, list): target = [target] for tgt in target: # NOTE: skip None list items # we do not want to skip False and 0! if tgt is None: continue cmd.append(to_str(tgt)) return " ".join(cmd) def is_supported(): """ Check the system for ZFS support """ # Check for supported platforms # NOTE: ZFS on Windows is in development # NOTE: ZFS on NetBSD is in development on_supported_platform = False if salt.utils.platform.is_sunos(): on_supported_platform = True elif salt.utils.platform.is_freebsd() and _check_retcode("kldstat -q -m zfs"): on_supported_platform = True elif salt.utils.platform.is_linux() and os.path.exists("/sys/module/zfs"): on_supported_platform = True elif salt.utils.platform.is_linux() and salt.utils.path.which("zfs-fuse"): on_supported_platform = True elif ( salt.utils.platform.is_darwin() and os.path.exists("/Library/Extensions/zfs.kext") and os.path.exists("/dev/zfs") ): on_supported_platform = True # Additional check for the zpool command return (salt.utils.path.which("zpool") and on_supported_platform) is True @real_memoize def has_feature_flags(): """ Check if zpool-features is available """ # get man location man = salt.utils.path.which("man") return _check_retcode(f"{man} zpool-features") if man else False @real_memoize def property_data_zpool(): """ Return a dict of zpool properties .. note:: Each property will have an entry with the following info: - edit : boolean - is this property editable after pool creation - type : str - either bool, bool_alt, size, numeric, or string - values : str - list of possible values .. warning:: This data is probed from the output of 'zpool get' with some supplemental data that is hardcoded. There is no better way to get this information aside from reading the code. """ # NOTE: man page also mentions a few short forms property_data = _property_parse_cmd( _zpool_cmd(), { "allocated": "alloc", "autoexpand": "expand", "autoreplace": "replace", "listsnapshots": "listsnaps", "fragmentation": "frag", }, ) # NOTE: zpool status/iostat has a few extra fields zpool_size_extra = [ "capacity-alloc", "capacity-free", "operations-read", "operations-write", "bandwidth-read", "bandwidth-write", "read", "write", ] zpool_numeric_extra = [ "cksum", "cap", ] for prop in zpool_size_extra: property_data[prop] = { "edit": False, "type": "size", "values": "<size>", } for prop in zpool_numeric_extra: property_data[prop] = { "edit": False, "type": "numeric", "values": "<count>", } return property_data @real_memoize def property_data_zfs(): """ Return a dict of zfs properties .. note:: Each property will have an entry with the following info: - edit : boolean - is this property editable after pool creation - inherit : boolean - is this property inheritable - type : str - either bool, bool_alt, size, numeric, or string - values : str - list of possible values .. warning:: This data is probed from the output of 'zfs get' with some supplemental data that is hardcoded. There is no better way to get this information aside from reading the code. """ return _property_parse_cmd( _zfs_cmd(), { "available": "avail", "logicalreferenced": "lrefer.", "logicalused": "lused.", "referenced": "refer", "volblocksize": "volblock", "compression": "compress", "readonly": "rdonly", "recordsize": "recsize", "refreservation": "refreserv", "reservation": "reserv", }, ) def from_numeric(value): """ Convert zfs numeric to python int """ if value == "none": value = None elif value: value = str_to_num(value) return value def to_numeric(value): """ Convert python int to zfs numeric """ value = from_numeric(value) if value is None: value = "none" return value def from_bool(value): """ Convert zfs bool to python bool """ if value in ["on", "yes"]: value = True elif value in ["off", "no"]: value = False elif value == "none": value = None return value def from_bool_alt(value): """ Convert zfs bool_alt to python bool """ return from_bool(value) def to_bool(value): """ Convert python bool to zfs on/off bool """ value = from_bool(value) if isinstance(value, bool): value = "on" if value else "off" elif value is None: value = "none" return value def to_bool_alt(value): """ Convert python to zfs yes/no value """ value = from_bool_alt(value) if isinstance(value, bool): value = "yes" if value else "no" elif value is None: value = "none" return value def from_size(value): """ Convert zfs size (human readable) to python int (bytes) """ match_size = re_zfs_size.match(str(value)) if match_size: v_unit = match_size.group(2).upper()[0] v_size = float(match_size.group(1)) v_multiplier = math.pow(1024, zfs_size.index(v_unit) + 1) value = v_size * v_multiplier if int(value) == value: value = int(value) elif value is not None: value = str(value) return from_numeric(value) def to_size(value, convert_to_human=True): """ Convert python int (bytes) to zfs size NOTE: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/pyzfs/common/util.py#114 """ value = from_size(value) if value is None: value = "none" if isinstance(value, Number) and value > 1024 and convert_to_human: v_power = int(math.floor(math.log(value, 1024))) v_multiplier = math.pow(1024, v_power) # NOTE: zfs is a bit odd on how it does the rounding, # see libzfs implementation linked above v_size_float = float(value) / v_multiplier if v_size_float == int(v_size_float): value = "{:.0f}{}".format( v_size_float, zfs_size[v_power - 1], ) else: for v_precision in ["{:.2f}{}", "{:.1f}{}", "{:.0f}{}"]: v_size = v_precision.format( v_size_float, zfs_size[v_power - 1], ) if len(v_size) <= 5: value = v_size break return value def from_str(value): """ Decode zfs safe string (used for name, path, ...) """ if value == "none": value = None if value: value = str(value) if value.startswith('"') and value.endswith('"'): value = value[1:-1] value = value.replace('\\"', '"') return value def to_str(value): """ Encode zfs safe string (used for name, path, ...) """ value = from_str(value) if value: value = value.replace('"', '\\"') if " " in value: value = '"' + value + '"' elif value is None: value = "none" return value def from_auto(name, value, source="auto"): """ Convert zfs value to python value """ return _auto("from", name, value, source) def to_auto(name, value, source="auto", convert_to_human=True): """ Convert python value to zfs value """ return _auto("to", name, value, source, convert_to_human) def from_auto_dict(values, source="auto"): """ Pass an entire dictionary to from_auto .. note:: The key will be passed as the name """ for name, value in values.items(): values[name] = from_auto(name, value, source) return values def to_auto_dict(values, source="auto", convert_to_human=True): """ Pass an entire dictionary to to_auto .. note:: The key will be passed as the name """ for name, value in values.items(): values[name] = to_auto(name, value, source, convert_to_human) return values def is_snapshot(name): """ Check if name is a valid snapshot name """ return from_str(name).count("@") == 1 def is_bookmark(name): """ Check if name is a valid bookmark name """ return from_str(name).count("#") == 1 def is_dataset(name): """ Check if name is a valid filesystem or volume name """ return not is_snapshot(name) and not is_bookmark(name) def zfs_command( command, flags=None, opts=None, property_name=None, property_value=None, filesystem_properties=None, target=None, ): """ Build and properly escape a zfs command .. note:: Input is not considered safe and will be passed through to_auto(from_auto('input_here')), you do not need to do so your self first. """ return _command( "zfs", command=command, flags=flags, opts=opts, property_name=property_name, property_value=property_value, filesystem_properties=filesystem_properties, pool_properties=None, target=target, ) def zpool_command( command, flags=None, opts=None, property_name=None, property_value=None, filesystem_properties=None, pool_properties=None, target=None, ): """ Build and properly escape a zpool command .. note:: Input is not considered safe and will be passed through to_auto(from_auto('input_here')), you do not need to do so your self first. """ return _command( "zpool", command=command, flags=flags, opts=opts, property_name=property_name, property_value=property_value, filesystem_properties=filesystem_properties, pool_properties=pool_properties, target=target, ) def parse_command_result(res, label=None): """ Parse the result of a zpool/zfs command .. note:: Output on failure is rather predictable. - retcode > 0 - each 'error' is a line on stderr - optional 'Usage:' block under those with hits We simple check those and return a OrderedDict were we set label = True|False and error = error_messages """ ret = OrderedDict() if label: ret[label] = res["retcode"] == 0 if res["retcode"] != 0: ret["error"] = [] for error in res["stderr"].splitlines(): if error.lower().startswith("usage:"): break if error.lower().startswith("use '-f'"): error = error.replace("-f", "force=True") if error.lower().startswith("use '-r'"): error = error.replace("-r", "recursive=True") ret["error"].append(error) if ret["error"]: ret["error"] = "\n".join(ret["error"]) else: del ret["error"] return ret